 core/includes/common.inc                           | 155 ++---
 .../Drupal/Core/Asset/CssCollectionRenderer.php    |  15 -
 .../lib/Drupal/Core/Asset/JsCollectionRenderer.php |   8 +-
 .../Drupal/Core/Asset/LibraryDiscoveryParser.php   |   6 -
 core/modules/comment/comment.libraries.yml         |   4 +-
 core/modules/history/history.libraries.yml         |   9 +-
 core/modules/history/history.module                |   7 +-
 core/modules/history/js/mark-as-read.js            |  19 +
 core/modules/history/src/Tests/HistoryTest.php     |   5 +-
 .../system/src/Tests/Ajax/FrameworkTest.php        |  34 +-
 .../system/src/Tests/Common/AttachedAssetsTest.php | 408 +++++++++++++
 .../src/Tests/Common/CascadingStylesheetsTest.php  | 185 ------
 .../system/src/Tests/Common/JavaScriptTest.php     | 660 ---------------------
 .../src/Tests/Common/MergeAttachmentsTest.php      |  94 ---
 .../modules/system/src/Tests/Common/RenderTest.php |  23 +-
 .../modules/ajax_test/ajax_test.libraries.yml      |  20 +
 .../tests/modules/ajax_test/ajax_test.module       |  23 +-
 .../modules/common_test/common_test.libraries.yml  | 103 ++++
 core/modules/system/theme.api.php                  |  28 +-
 core/modules/views/src/Tests/Plugin/CacheTest.php  |   5 +-
 core/modules/views/src/ViewExecutable.php          |  18 +-
 .../views_test_data.views_execution.inc            |   3 -
 .../Core/Asset/CssCollectionGrouperUnitTest.php    |  58 +-
 .../Core/Asset/CssCollectionRendererUnitTest.php   |  46 --
 .../Tests/Core/Asset/CssOptimizerUnitTest.php      |  81 +--
 .../Core/Asset/LibraryDiscoveryParserTest.php      |  35 +-
 .../Tests/Core/Asset/css_test_files/charset.css    |   1 +
 .../Asset/css_test_files/charset.css.optimized.css |   1 +
 .../Core/Asset/css_test_files/charset_newline.css  |   2 +
 .../Core/Asset/css_test_files/charset_sameline.css |   1 +
 .../charset_sameline.css.optimized.css             |   1 +
 .../library_test_files/external.libraries.yml      |   4 +-
 .../library_test_files/versions.libraries.yml      |  22 +
 33 files changed, 764 insertions(+), 1320 deletions(-)

diff --git a/core/includes/common.inc b/core/includes/common.inc
index 6125461..21bf10b 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -871,9 +871,7 @@ function _drupal_add_html_head_link($attributes, $header = FALSE) {
  * If CSS aggregation/compression is enabled, all cascading style sheets added
  * with $options['preprocess'] set to TRUE will be merged into one aggregate
  * file and compressed by removing all extraneous white space.
- * Preprocessed inline stylesheets will not be aggregated into this single file;
- * instead, they are just compressed upon output on the page. Externally hosted
- * stylesheets are never aggregated or compressed.
+ * Externally hosted stylesheets are never aggregated or compressed.
  *
  * The reason for aggregating the files is outlined quite thoroughly here:
  * http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due
@@ -899,18 +897,15 @@ function _drupal_add_html_head_link($attributes, $header = FALSE) {
  *     override module-supplied CSS files based on their filenames, and this
  *     prefixing helps prevent confusing name collisions for theme developers.
  *     See drupal_get_css() where the overrides are performed.
- *   - 'inline': A string of CSS that should be placed in the given scope. Note
- *     that it is better practice to use 'file' stylesheets, rather than
- *     'inline', as the CSS would then be aggregated and cached.
  *   - 'external': The absolute path to an external CSS file that is not hosted
  *     on the local server. These files will not be aggregated if CSS
  *     aggregation is enabled.
  * @param $options
  *   (optional) A string defining the 'type' of CSS that is being added in the
- *   $data parameter ('file', 'inline', or 'external'), or an array which can
- *   have any or all of the following keys:
- *   - 'type': The type of stylesheet being added. Available options are 'file',
- *     'inline' or 'external'. Defaults to 'file'.
+ *   $data parameter ('file' or 'external'), or an array which can have any or
+ *   all of the following keys:
+ *   - 'type': The type of stylesheet being added. Available options are 'file'
+ *     or 'external'. Defaults to 'file'.
  *   - 'basename': Force a basename for the file being added. Modules are
  *     expected to use stylesheets with unique filenames, but integration of
  *     external libraries may make this impossible. The basename of
@@ -1027,12 +1022,6 @@ function _drupal_add_css($data = NULL, $options = NULL) {
 
     // Add the data to the CSS array depending on the type.
     switch ($options['type']) {
-      case 'inline':
-        // For inline stylesheets, we don't want to use the $data as the array
-        // key as $data could be a very long string of CSS.
-        $css[] = $options;
-        break;
-
       case 'file':
         // Local CSS files are keyed by basename; if a file with the same
         // basename is added more than once, it gets overridden.
@@ -1278,21 +1267,12 @@ function drupal_clean_id_identifier($id) {
 }
 
 /**
- * Adds a JavaScript file, setting, or inline code to the page.
+ * Adds a JavaScript file or setting to the page.
  *
  * The behavior of this function depends on the parameters it is called with.
- * Generally, it handles the addition of JavaScript to the page, either as
- * reference to an existing file or as inline code. The following actions can be
- * performed using this function:
+ * Generally, it handles the addition of JavaScript to the page. The following
+ * actions can be performed using this function:
  * - Add a file ('file'): Adds a reference to a JavaScript file to the page.
- * - Add inline JavaScript code ('inline'): Executes a piece of JavaScript code
- *   on the current page by placing the code directly in the page (for example,
- *   to tell the user that a new message arrived, by opening a pop up, alert
- *   box, etc.). This should only be used for JavaScript that cannot be executed
- *   from a file. When adding inline code, make sure that you are not relying on
- *   $() being the jQuery function. Wrap your code in
- *   @code (function ($) {... })(jQuery); @endcode
- *   or use jQuery() instead of $().
  * - Add external JavaScript ('external'): Allows the inclusion of external
  *   JavaScript files that are not hosted on the local server. Note that these
  *   external JavaScript references do not get aggregated when preprocessing is
@@ -1305,10 +1285,6 @@ function drupal_clean_id_identifier($id) {
  * @code
  *   _drupal_add_js('core/misc/collapse.js');
  *   _drupal_add_js('core/misc/collapse.js', 'file');
- *   _drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', 'inline');
- *   _drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });',
- *     array('type' => 'inline', 'scope' => 'footer', 'weight' => 5)
- *   );
  *   _drupal_add_js('http://example.com/example.js', 'external');
  *   _drupal_add_js(array('myModule' => array('key' => 'value')), 'setting');
  * @endcode
@@ -1318,7 +1294,6 @@ function drupal_clean_id_identifier($id) {
  *
  * If JavaScript aggregation is enabled, all JavaScript files added with
  * $options['preprocess'] set to TRUE will be merged into one aggregate file.
- * Preprocessed inline JavaScript will not be aggregated into this single file.
  * Externally hosted JavaScripts are never aggregated.
  *
  * The reason for aggregating the files is outlined quite thoroughly here:
@@ -1338,7 +1313,6 @@ function drupal_clean_id_identifier($id) {
  *   (optional) If given, the value depends on the $options parameter, or
  *   $options['type'] if $options is passed as an associative array:
  *   - 'file': Path to the file relative to base_path().
- *   - 'inline': The JavaScript code that should be placed in the given scope.
  *   - 'external': The absolute path to an external JavaScript file that is not
  *     hosted on the local server. These files will not be aggregated if
  *     JavaScript aggregation is enabled.
@@ -1350,11 +1324,11 @@ function drupal_clean_id_identifier($id) {
  *     added to the existing settings array.
  * @param $options
  *   (optional) A string defining the type of JavaScript that is being added in
- *   the $data parameter ('file'/'setting'/'inline'/'external'), or an
- *   associative array. JavaScript settings should always pass the string
- *   'setting' only. Other types can have the following elements in the array:
+ *   the $data parameter ('file'/'setting'/'external'), or an assocative array.
+ *   JavaScript settings should always pass the string 'setting' only. Other
+ *   types can have the following elements in the array:
  *   - type: The type of JavaScript that is to be added to the page. Allowed
- *     values are 'file', 'inline', 'external' or 'setting'. Defaults
+ *     values are 'file', 'external' or 'setting'. Defaults
  *     to 'file'.
  *   - scope: The location in which you want to place the script. Possible
  *     values are 'header' or 'footer'. If your theme implements different
@@ -1467,15 +1441,9 @@ function _drupal_add_js($data = NULL, $options = NULL) {
             'data' => array(),
           );
         }
-        // All JavaScript settings are placed in the header of the page with
-        // the library weight so that inline scripts appear afterwards.
         $javascript['drupalSettings']['data'] = NestedArray::mergeDeepArray([$javascript['drupalSettings']['data'], $data], TRUE);
         break;
 
-      case 'inline':
-        $javascript[] = $options;
-        break;
-
       default: // 'file' and 'external'
         // Local and external files must keep their name as the associative key
         // so the same JavaScript file is not added twice.
@@ -1515,8 +1483,8 @@ function drupal_js_defaults($data = NULL) {
  *
  * References to JavaScript files are placed in a certain order: first, all
  * 'core' files, then all 'module' and finally all 'theme' JavaScript files
- * are added to the page. Then, all settings are output, followed by 'inline'
- * JavaScript code. If running update.php, all preprocessing is disabled.
+ * are added to the page. Then, all settings are output. If running update.php,
+ * all preprocessing is disabled.
  *
  * Note that hook_js_alter(&$javascript) is called during this function call
  * to allow alterations of the JavaScript during its presentation. Calls to
@@ -1694,44 +1662,40 @@ function drupal_merge_attached(array $a, array $b) {
 /**
  * Adds attachments to a render() structure.
  *
- * Libraries, JavaScript, CSS and other types of custom structures are attached
- * to elements using the #attached property. The #attached property is an
- * associative array, where the keys are the the attachment types and the values
- * are the attached data. For example:
+ * Libraries, JavaScript settings, feeds, HTML <head> tags and HTML <head> links
+ * are attached to elements using the #attached property. The #attached property
+ * is an associative array, where the keys are the the attachment types and the
+ * values are the attached data. For example:
  *
  * @code
- * $build['#attached'] = array(
- *   'library' => array(array('taxonomy', 'taxonomy')),
- *   'css' => array(drupal_get_path('module', 'taxonomy') . '/css/taxonomy.module.css'),
- * );
+ * $build['#attached'] = [
+ *   'library' => ['core/jquery']
+ * ];
  * @endcode
  *
- * 'js', 'css', and 'library' are types that get special handling. For any
- * other kind of attached data, the array key must be the full name of the
- * callback function and each value an array of arguments. For example:
+ * The available keys are:
+ * - 'library' (asset libraries)
+ * - 'drupalSettings' (JavaScript settings)
+ * - 'feed' (RSS feeds)
+ * - 'html_head' (tags in HTML <head>)
+ * - 'html_head_link' (<link> tags in HTML <head>)
+ * - 'http_header' (HTTP headers)
+ *
+ * For example:
  * @code
  * $build['#attached']['http_header'] = array(
  *   array('Content-Type', 'application/rss+xml; charset=utf-8'),
  * );
  * @endcode
  *
- * External 'js' and 'css' files can also be loaded. For example:
- * @code
- * $build['#attached']['js'] = array(
- *   'http://code.jquery.com/jquery-1.4.2.min.js' => array(
- *     'type' => 'external',
- *   ),
- * );
- * @endcode
- *
- * @param $elements
+ * @param array $elements
  *   The structured array describing the data being rendered.
- * @param $dependency_check
+ * @param bool $dependency_check
  *   When TRUE, will exit if a given library's dependencies are missing. When
  *   set to FALSE, will continue to add the libraries, even though one or more
  *   dependencies are missing. Defaults to FALSE.
  *
- * @return
+ * @return bool
  *   FALSE if there were any missing library dependencies; TRUE if all library
  *   dependencies were met.
  *
@@ -1740,12 +1704,10 @@ function drupal_merge_attached(array $a, array $b) {
  * @see _drupal_add_css()
  * @see drupal_render()
  */
-function drupal_process_attached($elements, $dependency_check = FALSE) {
+function drupal_process_attached(array $elements, $dependency_check = FALSE) {
   // Add defaults to the special attached structures that should be processed differently.
   $elements['#attached'] += array(
     'library' => array(),
-    'js' => array(),
-    'css' => array(),
   );
 
   // Add the libraries first.
@@ -1761,28 +1723,6 @@ function drupal_process_attached($elements, $dependency_check = FALSE) {
   }
   unset($elements['#attached']['library']);
 
-  // Add both the JavaScript and the CSS.
-  // The parameters for _drupal_add_js() and _drupal_add_css() require special
-  // handling.
-  foreach (array('js', 'css') as $type) {
-    foreach ($elements['#attached'][$type] as $data => $options) {
-      // If the value is not an array, it's a filename and passed as first
-      // (and only) argument.
-      if (!is_array($options)) {
-        $data = $options;
-        $options = NULL;
-      }
-      // In some cases, the first parameter ($data) is an array. Arrays can't be
-      // passed as keys in PHP, so we have to get $data from the value array.
-      if (is_numeric($data)) {
-        $data = $options['data'];
-        unset($options['data']);
-      }
-      call_user_func('_drupal_add_' . $type, $data, $options);
-    }
-    unset($elements['#attached'][$type]);
-  }
-
   // Convert every JavaScript settings asset into a regular JavaScript asset.
   // @todo Clean this up in https://www.drupal.org/node/2382533
   if (!empty($elements['#attached']['drupalSettings'])) {
@@ -1994,22 +1934,33 @@ function _drupal_add_library($library_name, $every_page = NULL) {
       // Add all components within the library.
       $elements['#attached'] = array(
         'library' => $library['dependencies'],
-        'js' => $library['js'],
-        'css' => $library['css'],
       );
       if (isset($library['drupalSettings'])) {
         $elements['#attached']['drupalSettings'] = $library['drupalSettings'];
       }
+      $added[$extension][$name] = drupal_process_attached($elements, TRUE);
+
+      // Add both the JavaScript and the CSS.
+      // The parameters for _drupal_add_js() and _drupal_add_css() require special
+      // handling.
       foreach (array('js', 'css') as $type) {
-        foreach ($elements['#attached'][$type] as $data => $options) {
-          // Set the every_page flag if one was passed.
-          if (isset($every_page)) {
-            $elements['#attached'][$type][$data]['every_page'] = $every_page;
+        foreach ($library[$type] as $data => $options) {
+          // If the value is not an array, it's a filename and passed as first
+          // (and only) argument.
+          if (!is_array($options)) {
+            $data = $options;
+            $options = NULL;
           }
+          // In some cases, the first parameter ($data) is an array. Arrays can't be
+          // passed as keys in PHP, so we have to get $data from the value array.
+          if (is_numeric($data)) {
+            $data = $options['data'];
+            unset($options['data']);
+          }
+          call_user_func('_drupal_add_' . $type, $data, $options);
         }
+        unset($elements['#attached'][$type]);
       }
-
-      $added[$extension][$name] = drupal_process_attached($elements, TRUE);
     }
     else {
       // Requested library does not exist.
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php
index ab07555..d00fcb4 100644
--- a/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php
+++ b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php
@@ -158,21 +158,6 @@ public function render(array $css_assets) {
           }
           break;
 
-        // Output a STYLE tag for an inline CSS asset. The asset's 'data'
-        // property contains the CSS content.
-        case 'inline':
-          $element = $style_element_defaults;
-          $element['#value'] = $css_asset['data'];
-          $element['#attributes']['media'] = $css_asset['media'];
-          $element['#browsers'] = $css_asset['browsers'];
-          // For inline CSS to validate as XHTML, all CSS containing XHTML needs
-          // to be wrapped in CDATA. To make that backwards compatible with HTML
-          // 4, we need to comment out the CDATA-tag.
-          $element['#value_prefix'] = "\n/* <![CDATA[ */\n";
-          $element['#value_suffix'] = "\n/* ]]> */\n";
-          $elements[] = $element;
-          break;
-
         // Output a LINK tag for an external CSS asset. The asset's 'data'
         // property contains the full URL.
         case 'external':
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
index 36f012b..52e6fe1 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
@@ -72,14 +72,8 @@ public function render(array $js_assets) {
           $element['#value_suffix'] = $embed_suffix;
           break;
 
-        case 'inline':
-          $element['#value_prefix'] = $embed_prefix;
-          $element['#value'] = $js_asset['data'];
-          $element['#value_suffix'] = $embed_suffix;
-          break;
-
         case 'file':
-          $query_string = empty($js_asset['version']) ? $default_query_string : 'v=' . $js_asset['version'];
+          $query_string = $js_asset['version'] == -1 ? $default_query_string : 'v=' . $js_asset['version'];
           $query_string_separator = (strpos($js_asset['data'], '?') !== FALSE) ? '&' : '?';
           $element['#attributes']['src'] = file_create_url($js_asset['data']);
           // Only add the cache-busting query string if this isn't an aggregate
diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
index ec5f457..5ff88b2 100644
--- a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
+++ b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
@@ -197,12 +197,6 @@ public function buildByExtension($extension) {
           $library[$type][] = $options;
         }
       }
-
-      // @todo Convert all uses of #attached[library][]=array('provider','name')
-      //   into #attached[library][]='provider/name' and remove this.
-      foreach ($library['dependencies'] as $i => $dependency) {
-        $library['dependencies'][$i] = $dependency;
-      }
     }
 
     return $libraries;
diff --git a/core/modules/comment/comment.libraries.yml b/core/modules/comment/comment.libraries.yml
index 46c98e7..d19ff93 100644
--- a/core/modules/comment/comment.libraries.yml
+++ b/core/modules/comment/comment.libraries.yml
@@ -30,7 +30,7 @@ drupal.comment-new-indicator:
     - core/jquery
     - core/jquery.once
     - core/drupal
-    - history/drupal.history
+    - history/api
     - core/drupal.displace
 
 drupal.node-new-comments-link:
@@ -41,4 +41,4 @@ drupal.node-new-comments-link:
     - core/jquery
     - core/jquery.once
     - core/drupal
-    - history/drupal.history
+    - history/api
diff --git a/core/modules/history/history.libraries.yml b/core/modules/history/history.libraries.yml
index 4920096..457b1f4 100644
--- a/core/modules/history/history.libraries.yml
+++ b/core/modules/history/history.libraries.yml
@@ -1,4 +1,4 @@
-drupal.history:
+api:
   version: VERSION
   js:
     js/history.js: {}
@@ -7,3 +7,10 @@ drupal.history:
     - core/drupalSettings
     - core/drupal
     - core/drupal.ajax
+
+mark-as-read:
+  version: VERSION
+  js:
+    js/mark-as-read.js: { scope: footer }
+  dependencies:
+    - history/api
diff --git a/core/modules/history/history.module b/core/modules/history/history.module
index bd8ca71..61395de 100644
--- a/core/modules/history/history.module
+++ b/core/modules/history/history.module
@@ -138,11 +138,8 @@ function history_node_view_alter(array &$build, EntityInterface $node, EntityVie
     // When the window's "load" event is triggered, mark the node as read.
     // This still allows for Drupal behaviors (which are triggered on the
     // "DOMContentReady" event) to add "new" and "updated" indicators.
-    $build['#attached']['js'][] = array(
-      'data' => 'window.addEventListener("load",function(){Drupal.history.markAsRead(' . $node->id() . ');},false);',
-      'type' => 'inline',
-    );
-    $build['#attached']['library'][] = 'history/drupal.history';
+    $build['#attached']['library'][] = 'history/mark-as-read';
+    $build['#attached']['drupalSettings']['history']['nodesToMarkAsRead'][$node->id()] = TRUE;
   }
 
 }
diff --git a/core/modules/history/js/mark-as-read.js b/core/modules/history/js/mark-as-read.js
new file mode 100644
index 0000000..d5b62ec
--- /dev/null
+++ b/core/modules/history/js/mark-as-read.js
@@ -0,0 +1,19 @@
+/**
+ * Marks the nodes listed in drupalSettings.history.nodesToMarkAsRead as read.
+ *
+ * Uses the History module JavaScript API.
+ */
+(function (window, Drupal, drupalSettings) {
+
+  "use strict";
+
+  // When the window's "load" event is triggered, mark all enumerated nodes as
+  // read. This still allows for Drupal behaviors (which are triggered on the
+  // "DOMContentReady" event) to add "new" and "updated" indicators.
+  window.addEventListener('load', function() {
+    if (drupalSettings.history && drupalSettings.history.nodesToMarkAsRead) {
+      Object.keys(drupalSettings.history.nodesToMarkAsRead).forEach(Drupal.history.markAsRead);
+    }
+  });
+
+})(window, Drupal, drupalSettings);
diff --git a/core/modules/history/src/Tests/HistoryTest.php b/core/modules/history/src/Tests/HistoryTest.php
index 3acd3c4..cdbfd68 100644
--- a/core/modules/history/src/Tests/HistoryTest.php
+++ b/core/modules/history/src/Tests/HistoryTest.php
@@ -119,8 +119,9 @@ function testHistory() {
     $this->drupalGet('node/' . $nid);
     // JavaScript present to record the node read.
     $settings = $this->getDrupalSettings();
-    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/history/js/history.js']), 'drupal.history library is present.');
-    $this->assertRaw('Drupal.history.markAsRead(' . $nid . ')', 'History module JavaScript API call to mark node as read present on page.');
+    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/history/js/history.js']), 'history/api library is present.');
+    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/history/js/mark-as-read.js']), 'history/mark-as-read library is present.');
+    $this->assertEqual([$nid => TRUE], $settings['history']['nodesToMarkAsRead'], 'drupalSettings to mark node as read are present.');
 
     // Simulate JavaScript: perform HTTP request to mark node as read.
     $response = $this->markNodeAsRead($nid);
diff --git a/core/modules/system/src/Tests/Ajax/FrameworkTest.php b/core/modules/system/src/Tests/Ajax/FrameworkTest.php
index d998f01..cded8cb 100644
--- a/core/modules/system/src/Tests/Ajax/FrameworkTest.php
+++ b/core/modules/system/src/Tests/Ajax/FrameworkTest.php
@@ -35,44 +35,18 @@ public function testAJAXRender() {
    * Tests AjaxResponse::prepare() AJAX commands ordering.
    */
   public function testOrder() {
-    $path = drupal_get_path('module', 'system');
     $expected_commands = array();
 
     // Expected commands, in a very specific order.
     $expected_commands[0] = new SettingsCommand(array('ajax' => 'test'), TRUE);
     drupal_static_reset('_drupal_add_css');
-    $attached = array(
-      '#attached' => array(
-        'css' => array(
-          $path . '/css/system.admin.css' => array(),
-          $path . '/css/system.maintenance.css' => array()
-        ),
-      ),
-    );
-    drupal_render($attached);
-    drupal_process_attached($attached);
+    $build['#attached']['library'][] = 'ajax_test/order-css-command';
+    drupal_process_attached($build);
     $expected_commands[1] = new AddCssCommand(drupal_get_css(_drupal_add_css(), TRUE));
     drupal_static_reset('_drupal_add_js');
-    $attached = array(
-      '#attached' => array(
-        'js' => array(
-          $path . '/system.js' => array(),
-        ),
-      ),
-    );
-    drupal_render($attached);
-    drupal_process_attached($attached);
+    $build['#attached']['library'][] = 'ajax_test/order-js-command';
+    drupal_process_attached($build);
     $expected_commands[2] = new PrependCommand('head', drupal_get_js('header', _drupal_add_js(), TRUE));
-    drupal_static_reset('_drupal_add_js');
-    $attached = array(
-      '#attached' => array(
-        'js' => array(
-          $path . '/system.modules.js' => array('scope' => 'footer'),
-        ),
-      ),
-    );
-    drupal_render($attached);
-    drupal_process_attached($attached);
     $expected_commands[3] = new AppendCommand('body', drupal_get_js('footer', _drupal_add_js(), TRUE));
     $expected_commands[4] = new HtmlCommand('body', 'Hello, world!');
 
diff --git a/core/modules/system/src/Tests/Common/AttachedAssetsTest.php b/core/modules/system/src/Tests/Common/AttachedAssetsTest.php
new file mode 100644
index 0000000..fd44155
--- /dev/null
+++ b/core/modules/system/src/Tests/Common/AttachedAssetsTest.php
@@ -0,0 +1,408 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Common\JavaScriptTest.
+ */
+
+namespace Drupal\system\Tests\Common;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\Crypt;
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Tests #attached assets: attached asset libraries and JavaScript settings.
+ *
+ * i.e. tests:
+ *
+ * @code
+ * $build['#attached']['library'] = …
+ * $build['#attached']['drupalSettings'] = …
+ * @endcode
+ *
+ * @group Common
+ * @group Asset
+ */
+class AttachedAssetsTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('language', 'simpletest', 'common_test', 'system');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Disable preprocessing.
+    \Drupal::config('system.performance')
+      ->set('css.preprocess', FALSE)
+      ->set('js.preprocess', FALSE)
+      ->save();
+
+    // Reset _drupal_add_css() and _drupal_add_js() statics before each test.
+    drupal_static_reset('_drupal_add_css');
+    drupal_static_reset('_drupal_add_js');
+
+    $this->installSchema('system', 'router');
+    \Drupal::service('router.builder')->rebuild();
+  }
+
+  /**
+   * Tests that default CSS and JavaScript is empty.
+   */
+  function testDefault() {
+    $build['#attached'] = [];
+    drupal_process_attached($build);
+
+    $this->assertEqual(array(), _drupal_add_css(), 'Default CSS is empty.');
+    $this->assertEqual(array(), _drupal_add_js(), 'Default JavaScript is empty.');
+  }
+
+  /**
+   * Tests non-existing libraries.
+   */
+  function testLibraryUnknown() {
+    $build['#attached']['library'][] = 'unknown/unknown';
+    drupal_process_attached($build);
+
+    $scripts = drupal_get_js();
+    $this->assertTrue(strpos($scripts, 'unknown') === FALSE, 'Unknown library was not added to the page.');
+  }
+
+  /**
+   * Tests adding a CSS and a JavaScript file.
+   */
+  function testAddFiles() {
+    $build['#attached']['library'][] = 'common_test/files';
+    drupal_process_attached($build);
+
+    $css = _drupal_add_css();
+    $js = _drupal_add_js();
+    $this->assertTrue(array_key_exists('bar.css', $css), 'CSS files are correctly added.');
+    $this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/foo.js', $js), 'JavaScript files are correctly added.');
+
+    $rendered_css = drupal_get_css();
+    $rendered_js = drupal_get_js();
+    $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
+    $this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . file_create_url('core/modules/system/tests/modules/common_test/bar.css') . '?' . $query_string . '" media="all" />'), FALSE, 'Rendering an external CSS file.');
+    $this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/foo.js') . '?' . $query_string . '"></script>'), FALSE, 'Rendering an external JavaScript file.');
+  }
+
+  /**
+   * Tests adding JavaScript settings.
+   */
+  function testAddJsSettings() {
+    // Add a file in order to test default settings.
+    $build['#attached']['library'][] = 'core/drupalSettings';
+    drupal_process_attached($build);
+
+    $javascript = _drupal_add_js();
+    $this->assertTrue(array_key_exists('currentPath', $javascript['drupalSettings']['data']['path']), 'The current path JavaScript setting is set correctly.');
+
+    $javascript = _drupal_add_js(array('drupal' => 'rocks', 'dries' => 280342800), 'setting');
+    $this->assertEqual(280342800, $javascript['drupalSettings']['data']['dries'], 'JavaScript setting is set correctly.');
+    $this->assertEqual('rocks', $javascript['drupalSettings']['data']['drupal'], 'The other JavaScript setting is set correctly.');
+  }
+
+  /**
+   * Tests adding external CSS and JavaScript files.
+   */
+  function testAddExternalFiles() {
+    $build['#attached']['library'][] = 'common_test/external';
+    drupal_process_attached($build);
+
+    $css = _drupal_add_css();
+    $js = _drupal_add_js();
+    $this->assertTrue(array_key_exists('http://example.com/stylesheet.css', $css), 'External CSS files are correctly added.');
+    $this->assertTrue(array_key_exists('http://example.com/script.js', $js), 'External JavaScript files are correctly added.');
+
+    $rendered_css = drupal_get_css();
+    $rendered_js = drupal_get_js();
+    $this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="http://example.com/stylesheet.css" media="all" />'), FALSE, 'Rendering an external CSS file.');
+    $this->assertNotIdentical(strpos($rendered_js, '<script src="http://example.com/script.js"></script>'), FALSE, 'Rendering an external JavaScript file.');
+  }
+
+  /**
+   * Tests adding JavaScript files with additional attributes.
+   */
+  function testAttributes() {
+    $build['#attached']['library'][] = 'common_test/js-attributes';
+    drupal_process_attached($build);
+
+    $rendered_js = drupal_get_js();
+    $expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
+    $expected_2 = '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js') . '?v=1" defer bar="foo"></script>';
+    $this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered external JavaScript with correct defer and random attributes.');
+    $this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered internal JavaScript with correct defer and random attributes.');
+  }
+
+  /**
+   * Tests that attributes are maintained when JS aggregation is enabled.
+   */
+  function testAggregatedAttributes() {
+    // Enable aggregation.
+    \Drupal::config('system.performance')->set('js.preprocess', 1)->save();
+
+    $build['#attached']['library'][] = 'common_test/js-attributes';
+    drupal_process_attached($build);
+
+    $rendered_js = drupal_get_js();
+    $expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
+    $expected_2 = '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js') . '?v=1" defer bar="foo"></script>';
+    $this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered external JavaScript with correct defer and random attributes.');
+    $this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered internal JavaScript with correct defer and random attributes.');
+  }
+
+  /**
+   * Tests drupal_get_js() for JavaScript settings.
+   */
+  function testHeaderSetting() {
+    $build = array();
+    $build['#attached']['library'][] = 'core/drupalSettings';
+    // Nonsensical value to verify if it's possible to override path settings.
+    $build['#attached']['drupalSettings']['path']['pathPrefix'] = 'yarhar';
+    drupal_process_attached($build);
+
+    $rendered_js = drupal_get_js('header');
+
+    // Parse the generated drupalSettings <script> back to a PHP representation.
+    $startToken = 'drupalSettings = ';
+    $endToken = '}';
+    $start = strpos($rendered_js, $startToken) + strlen($startToken);
+    $end = strrpos($rendered_js, $endToken);
+    $json  = Unicode::substr($rendered_js, $start, $end - $start + 1);
+    $parsed_settings = Json::decode($json);
+
+    // Test whether the settings for core/drupalSettings are available.
+    $this->assertTrue(isset($parsed_settings['path']['baseUrl']), 'drupalSettings.path.baseUrl is present.');
+    $this->assertTrue(isset($parsed_settings['path']['scriptPath']), 'drupalSettings.path.scriptPath is present.');
+    $this->assertIdentical($parsed_settings['path']['pathPrefix'], 'yarhar', 'drupalSettings.path.pathPrefix is present and has the correct (overridden) value.');
+    $this->assertIdentical($parsed_settings['path']['currentPath'], '', 'drupalSettings.path.currentPath is present and has the correct value.');
+    $this->assertIdentical($parsed_settings['path']['currentPathIsAdmin'], FALSE, 'drupalSettings.path.currentPathIsAdmin is present and has the correct value.');
+    $this->assertIdentical($parsed_settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is present and has the correct value.');
+    $this->assertIdentical($parsed_settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is present and has the correct value.');
+
+    // Tests whether altering JavaScript settings via hook_js_settings_alter()
+    // is working as expected.
+    // @see common_test_js_settings_alter()
+    $this->assertIdentical($parsed_settings['locale']['pluralDelimiter'], '☃');
+    $this->assertIdentical($parsed_settings['foo'], 'bar');
+  }
+
+  /**
+   * Tests JS assets assigned to the 'footer' scope.
+   */
+  function testFooterHTML() {
+    $build['#attached']['library'][] = 'common_test/js-footer';
+    drupal_process_attached($build);
+
+    $rendered_js = drupal_get_js('footer');
+    $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
+    $this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/footer.js') . '?' . $query_string . '"></script>'), FALSE, 'Rendering an external JavaScript file.');
+  }
+
+  /**
+   * Tests _drupal_add_js() sets preprocess to FALSE when cache is also FALSE.
+   */
+  function testNoCache() {
+    $build['#attached']['library'][] = 'common_test/no-cache';
+    drupal_process_attached($build);
+
+    $js = _drupal_add_js();
+    $this->assertFalse($js['core/modules/system/tests/modules/common_test/nocache.js']['preprocess'], 'Setting cache to FALSE sets preprocess to FALSE when adding JavaScript.');
+  }
+
+  /**
+   * Tests adding JavaScript within conditional comments.
+   *
+   * @see \Drupal\Core\Render\Element\HtmlTag::preRenderConditionalComments()
+   */
+  function testBrowserConditionalComments() {
+    $default_query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
+
+    $build['#attached']['library'][] = 'common_test/browsers';
+    drupal_process_attached($build);
+
+    $js = drupal_get_js();
+    $expected_1 = "<!--[if lte IE 8]>\n" . '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/old-ie.js') . '?' . $default_query_string . '"></script>' . "\n<![endif]-->";
+    $expected_2 = "<!--[if !IE]><!-->\n" . '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/no-ie.js') . '?' . $default_query_string . '"></script>' . "\n<!--<![endif]-->";
+
+    $this->assertNotIdentical(strpos($js, $expected_1), FALSE, 'Rendered JavaScript within downlevel-hidden conditional comments.');
+    $this->assertNotIdentical(strpos($js, $expected_2), FALSE, 'Rendered JavaScript within downlevel-revealed conditional comments.');
+  }
+
+  /**
+   * Tests JavaScript versioning.
+   */
+  function testVersionQueryString() {
+    $build['#attached']['library'][] = 'core/backbone';
+    $build['#attached']['library'][] = 'core/domready';
+    drupal_process_attached($build);
+
+    $js = drupal_get_js();
+    $this->assertTrue(strpos($js, 'core/assets/vendor/backbone/backbone.js?v=1.1.0') > 0 && strpos($js, 'core/assets/vendor/domready/ready.min.js?v=1.0.6') > 0 , 'JavaScript version identifiers correctly appended to URLs');
+  }
+
+  /**
+   * Tests JavaScript and CSS asset ordering.
+   */
+  function testRenderOrder() {
+    $build['#attached']['library'][] = 'common_test/order';
+    drupal_process_attached($build);
+
+    // Construct the expected result from the regex.
+    $expected_order_js = [
+      "-8_1",
+      "-8_2",
+      "-8_3",
+      "-8_4",
+      "-5_1", // The external script.
+      "-3_1",
+      "-3_2",
+      "0_1",
+      "0_2",
+      "0_3",
+    ];
+
+    // Retrieve the rendered JavaScript and test against the regex.
+    $rendered_js = drupal_get_js();
+    $matches = array();
+    if (preg_match_all('/weight_([-0-9]+_[0-9]+)/', $rendered_js, $matches)) {
+      $result = $matches[1];
+    }
+    else {
+      $result = array();
+    }
+    $this->assertIdentical($result, $expected_order_js, 'JavaScript is added in the expected weight order.');
+
+    // Construct the expected result from the regex.
+    $expected_order_css = [
+      // Base.
+      'base_weight_-101_1',
+      'base_weight_-8_1',
+      'layout_weight_-101_1',
+      'base_weight_0_1',
+      'base_weight_0_2',
+      // Layout.
+      'layout_weight_-8_1',
+      'component_weight_-101_1',
+      'layout_weight_0_1',
+      'layout_weight_0_2',
+      // Component.
+      'component_weight_-8_1',
+      'state_weight_-101_1',
+      'component_weight_0_1',
+      'component_weight_0_2',
+      // State.
+      'state_weight_-8_1',
+      'theme_weight_-101_1',
+      'state_weight_0_1',
+      'state_weight_0_2',
+      // Theme.
+      'theme_weight_-8_1',
+      'theme_weight_0_1',
+      'theme_weight_0_2',
+    ];
+
+    // Retrieve the rendered JavaScript and test against the regex.
+    $rendered_css = drupal_get_css();
+    $matches = array();
+    if (preg_match_all('/([a-z]+)_weight_([-0-9]+_[0-9]+)/', $rendered_css, $matches)) {
+      $result = $matches[0];
+    }
+    else {
+      $result = array();
+    }
+    $this->assertIdentical($result, $expected_order_css, 'CSS is added in the expected weight order.');
+  }
+
+  /**
+   * Tests rendering the JavaScript with a file's weight above jQuery's.
+   */
+  function testRenderDifferentWeight() {
+    // If a library contains assets A and B, and A is listed first, then B can
+    // still make itself appear first by defining a lower weight.
+    $build['#attached']['library'][] = 'core/jquery';
+    $build['#attached']['library'][] = 'common_test/weight';
+    drupal_process_attached($build);
+
+    $js = drupal_get_js();
+    $this->assertTrue(strpos($js, 'lighter.css') < strpos($js, 'first.js'), 'Lighter CSS assets are rendered first.');
+    $this->assertTrue(strpos($js, 'lighter.js') < strpos($js, 'first.js'), 'Lighter JavaScript assets are rendered first.');
+    $this->assertTrue(strpos($js, 'before-jquery.js') < strpos($js, 'core/assets/vendor/jquery/jquery.js'), 'Rendering a JavaScript file above jQuery.');
+  }
+
+  /**
+   * Tests altering a JavaScript's weight via hook_js_alter().
+   *
+   * @see simpletest_js_alter()
+   */
+  function testAlter() {
+    // Add both tableselect.js and simpletest.js.
+    $build['#attached']['library'][] = 'core/drupal.tableselect';
+    $build['#attached']['library'][] = 'simpletest/drupal.simpletest';
+    drupal_process_attached($build);
+
+    // Render the JavaScript, testing if simpletest.js was altered to be before
+    // tableselect.js. See simpletest_js_alter() to see where this alteration
+    // takes place.
+    $js = drupal_get_js();
+    $this->assertTrue(strpos($js, 'simpletest.js') < strpos($js, 'core/misc/tableselect.js'), 'Altering JavaScript weight through the alter hook.');
+  }
+
+  /**
+   * Adds a JavaScript library to the page and alters it.
+   *
+   * @see common_test_library_info_alter()
+   */
+  function testLibraryAlter() {
+    // Verify that common_test altered the title of Farbtastic.
+    /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
+    $library_discovery = \Drupal::service('library.discovery');
+    $library = $library_discovery->getLibraryByName('core', 'jquery.farbtastic');
+    $this->assertEqual($library['version'], '0.0', 'Registered libraries were altered.');
+
+    // common_test_library_info_alter() also added a dependency on jQuery Form.
+    $build['#attached']['library'][] = 'core/jquery.farbtastic';
+    drupal_process_attached($build);
+    $scripts = drupal_get_js();
+    $this->assertTrue(strpos($scripts, 'core/assets/vendor/jquery-form/jquery.form.js'), 'Altered library dependencies are added to the page.');
+  }
+
+  /**
+   * Tests that multiple modules can implement libraries with the same name.
+   *
+   * @see common_test.library.yml
+   */
+  function testLibraryNameConflicts() {
+    /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
+    $library_discovery = \Drupal::service('library.discovery');
+    $farbtastic = $library_discovery->getLibraryByName('common_test', 'jquery.farbtastic');
+    $this->assertEqual($farbtastic['version'], '0.1', 'Alternative libraries can be added to the page.');
+  }
+
+  /**
+   * Tests JavaScript files that have querystrings attached get added right.
+   */
+  function testAddJsFileWithQueryString() {
+    $build['#attached']['library'][] = 'common_test/querystring';
+    drupal_process_attached($build);
+
+    $css = _drupal_add_css();
+    $js = _drupal_add_js();
+    $this->assertTrue(array_key_exists('querystring.css?arg1=value1&arg2=value2', $css), 'CSS file with query string is correctly added.');
+    $this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2', $js), 'JavaScript file with query string is correctly added.');
+
+    $rendered_css = drupal_get_css();
+    $rendered_js = drupal_get_js();
+    $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
+    $this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . str_replace('&', '&amp;', file_create_url('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2')) . '&amp;' . $query_string . '" media="all" />'), FALSE, 'CSS file with query string gets version query string correctly appended..');
+    $this->assertNotIdentical(strpos($rendered_js, '<script src="' . str_replace('&', '&amp;', file_create_url('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2')) . '&amp;' . $query_string . '"></script>'), FALSE, 'JavaScript file with query string gets version query string correctly appended.');
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Common/CascadingStylesheetsTest.php b/core/modules/system/src/Tests/Common/CascadingStylesheetsTest.php
deleted file mode 100644
index 658cc29..0000000
--- a/core/modules/system/src/Tests/Common/CascadingStylesheetsTest.php
+++ /dev/null
@@ -1,185 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\system\Tests\Common\CascadingStylesheetsTest.
- */
-
-namespace Drupal\system\Tests\Common;
-
-use Drupal\Component\Utility\String;
-use Drupal\simpletest\KernelTestBase;
-
-/**
- * Tests adding various cascading stylesheets to the page.
- *
- * @group Common
- */
-class CascadingStylesheetsTest extends KernelTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('language', 'system');
-
-  protected function setUp() {
-    parent::setUp();
-    // Reset _drupal_add_css() before each test.
-    drupal_static_reset('_drupal_add_css');
-  }
-
-  /**
-   * Checks that default stylesheets are empty.
-   */
-  function testDefault() {
-    $this->assertEqual(array(), _drupal_add_css(), 'Default CSS is empty.');
-  }
-
-  /**
-   * Tests adding a file stylesheet.
-   */
-  function testAddFile() {
-    $path = drupal_get_path('module', 'simpletest') . '/css/simpletest.module.css';
-    $css = _drupal_add_css($path);
-    $this->assertEqual($css['simpletest.module.css']['data'], $path);
-  }
-
-  /**
-   * Tests adding an external stylesheet.
-   */
-  function testAddExternal() {
-    $path = 'http://example.com/style.css';
-    $css = _drupal_add_css($path, 'external');
-    $this->assertEqual($css[$path]['type'], 'external', 'Adding an external CSS file caches it properly.');
-  }
-
-  /**
-   * Makes sure that resetting the CSS empties the cache.
-   */
-  function testReset() {
-    drupal_static_reset('_drupal_add_css');
-    $this->assertEqual(array(), _drupal_add_css(), 'Resetting the CSS empties the cache.');
-  }
-
-  /**
-   * Tests rendering the stylesheets.
-   */
-  function testRenderFile() {
-    $css = drupal_get_path('module', 'simpletest') . '/css/simpletest.module.css';
-    _drupal_add_css($css);
-    $styles = drupal_get_css(NULL, FALSE, FALSE);
-    $this->assertTrue(strpos($styles, $css) > 0, 'Rendered CSS includes the added stylesheet.');
-    // Verify that newlines are properly added inside style tags.
-    $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
-    $css_processed = '<link rel="stylesheet" href="' . String::checkPlain(file_create_url($css)) . "?" . $query_string . '" media="all" />';
-    $this->assertEqual(trim($styles), $css_processed, 'Rendered CSS includes newlines inside style tags for JavaScript use.');
-  }
-
-  /**
-   * Tests rendering an external stylesheet.
-   */
-  function testRenderExternal() {
-    $css = 'http://example.com/style.css';
-    _drupal_add_css($css, 'external');
-    $styles = drupal_get_css();
-    // Stylesheet URL may be the href of a LINK tag or in an @import statement
-    // of a STYLE tag.
-    $this->assertTrue(strpos($styles, 'href="' . $css) > 0 || strpos($styles, '@import url("' . $css . '")') > 0, 'Rendering an external CSS file.');
-  }
-
-  /**
-   * Tests rendering inline stylesheets with preprocessing on.
-   */
-  function testRenderInlinePreprocess() {
-    // Turn on CSS aggregation to allow for preprocessing.
-    $config = $this->container->get('config.factory')->get('system.performance');
-    $config->set('css.preprocess', 1);
-
-    $css = 'body { padding: 0px; }';
-    $css_preprocessed = '<style media="all">' . "\n/* <![CDATA[ */\n" . "body{padding:0px;}\n" . "\n/* ]]> */\n" . '</style>';
-    _drupal_add_css($css, array('type' => 'inline'));
-    $styles = drupal_get_css(NULL, NULL, FALSE);
-    $this->assertEqual(trim($styles), $css_preprocessed, 'Rendering preprocessed inline CSS adds it to the page.');
-  }
-
-  /**
-   * Tests rendering inline stylesheets with preprocessing off.
-   */
-  function testRenderInlineNoPreprocess() {
-    $css = 'body { padding: 0px; }';
-    _drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE));
-    $styles = drupal_get_css();
-    $this->assertTrue(strpos($styles, $css) > 0, 'Rendering non-preprocessed inline CSS adds it to the page.');
-  }
-
-  /**
-   * Tests CSS ordering.
-   */
-  function testRenderOrder() {
-    // Load a module CSS file.
-    _drupal_add_css(drupal_get_path('module', 'simpletest') . '/css/simpletest.module.css');
-    // Load a few system CSS files in a custom, early-loading aggregate group.
-    $test_aggregate_group = -100;
-    $system_path = drupal_get_path('module', 'system');
-    _drupal_add_css($system_path . '/css/system.module.css', array('group' => $test_aggregate_group, 'weight' => -10));
-    _drupal_add_css($system_path . '/css/system.theme.css', array('group' => $test_aggregate_group));
-
-    $expected = array(
-      $system_path . '/css/system.module.css',
-      $system_path . '/css/system.theme.css',
-      drupal_get_path('module', 'simpletest') . '/css/simpletest.module.css',
-    );
-
-    $styles = drupal_get_css(NULL, NULL, FALSE);
-    // Stylesheet URL may be the href of a LINK tag or in an @import statement
-    // of a STYLE tag.
-    if (preg_match_all('/(href="|url\(")' . preg_quote($GLOBALS['base_url'] . '/', '/') . '([^?]+)\?/', $styles, $matches)) {
-      $result = $matches[2];
-    }
-    else {
-      $result = array();
-    }
-
-    $this->assertIdentical($result, $expected, 'The CSS files are in the expected order.');
-  }
-
-  /**
-   * Tests CSS override.
-   */
-  function testRenderOverride() {
-    $system = drupal_get_path('module', 'system');
-
-    _drupal_add_css($system . '/css/system.module.css');
-    _drupal_add_css($system . '/tests/css/system.module.css');
-
-    // The dummy stylesheet should be the only one included.
-    $styles = drupal_get_css();
-    $this->assert(strpos($styles, $system . '/tests/css/system.module.css') !== FALSE, 'The overriding CSS file is output.');
-    $this->assert(strpos($styles, $system . '/css/system.module.css') === FALSE, 'The overridden CSS file is not output.');
-
-    _drupal_add_css($system . '/tests/css/system.module.css');
-    _drupal_add_css($system . '/css/system.module.css');
-
-    // The standard stylesheet should be the only one included.
-    $styles = drupal_get_css();
-    $this->assert(strpos($styles, $system . '/css/system.module.css') !== FALSE, 'The overriding CSS file is output.');
-    $this->assert(strpos($styles, $system . '/tests/css/system.module.css') === FALSE, 'The overridden CSS file is not output.');
-  }
-
-  /**
-   * Tests that CSS query string remains intact when added to file.
-   */
-  function testAddCssFileWithQueryString() {
-    $css_without_query_string = drupal_get_path('module', 'node') . '/css/node.admin.css';
-    $css_with_query_string = '/' . drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&arg2=value2';
-    _drupal_add_css($css_without_query_string);
-    _drupal_add_css($css_with_query_string);
-
-    $styles = drupal_get_css();
-    $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
-    $this->assertTrue(strpos($styles, $css_without_query_string . '?' . $query_string), 'Query string was appended correctly to css.');
-    $this->assertTrue(strpos($styles, str_replace('&', '&amp;', $css_with_query_string)), 'Query string not escaped on a URI.');
-  }
-}
diff --git a/core/modules/system/src/Tests/Common/JavaScriptTest.php b/core/modules/system/src/Tests/Common/JavaScriptTest.php
deleted file mode 100644
index 9c924fd..0000000
--- a/core/modules/system/src/Tests/Common/JavaScriptTest.php
+++ /dev/null
@@ -1,660 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\system\Tests\Common\JavaScriptTest.
- */
-
-namespace Drupal\system\Tests\Common;
-
-use Drupal\Component\Serialization\Json;
-use Drupal\Component\Utility\Unicode;
-use Drupal\simpletest\KernelTestBase;
-use Drupal\Component\Utility\Crypt;
-
-/**
- * Tests the JavaScript system.
- *
- * @group Common
- */
-class JavaScriptTest extends KernelTestBase {
-
-  /**
-   * Enable Language and SimpleTest in the test environment.
-   *
-   * @var array
-   */
-  public static $modules = array('language', 'simpletest', 'common_test', 'system');
-
-  /**
-   * Stores configured value for JavaScript preprocessing.
-   */
-  protected $preprocess_js = NULL;
-
-  protected function setUp() {
-    parent::setUp();
-
-    // Disable preprocessing
-    $config = \Drupal::config('system.performance');
-    $this->preprocess_js = $config->get('js.preprocess');
-    $config->set('js.preprocess', 0);
-    $config->save();
-
-    // Reset _drupal_add_js() statics before each test.
-    drupal_static_reset('_drupal_add_js');
-
-    $this->installSchema('system', 'router');
-    \Drupal::service('router.builder')->rebuild();
-  }
-
-  protected function tearDown() {
-    // Restore configured value for JavaScript preprocessing.
-    $config = \Drupal::config('system.performance');
-    $config->set('js.preprocess', $this->preprocess_js);
-    $config->save();
-    parent::tearDown();
-  }
-
-  /**
-   * Tests that default JavaScript is empty.
-   */
-  function testDefault() {
-    $this->assertEqual(array(), _drupal_add_js(), 'Default JavaScript is empty.');
-  }
-
-  /**
-   * Tests adding a JavaScript file.
-   */
-  function testAddFile() {
-    $attached['#attached']['js']['core/misc/collapse.js'] = array();
-    $this->render($attached);
-    $javascript = _drupal_add_js();
-    $this->assertTrue(array_key_exists('core/misc/collapse.js', $javascript), 'JavaScript files are correctly added.');
-  }
-
-  /**
-   * Tests adding settings.
-   */
-  function testAddSetting() {
-    // Add a file in order to test default settings.
-    $build['#attached']['library'][] = 'core/drupalSettings';
-    drupal_process_attached($build);
-    $javascript = _drupal_add_js();
-    $this->assertTrue(array_key_exists('currentPath', $javascript['drupalSettings']['data']['path']), 'The current path JavaScript setting is set correctly.');
-
-    $javascript = _drupal_add_js(array('drupal' => 'rocks', 'dries' => 280342800), 'setting');
-    $this->assertEqual(280342800, $javascript['drupalSettings']['data']['dries'], 'JavaScript setting is set correctly.');
-    $this->assertEqual('rocks', $javascript['drupalSettings']['data']['drupal'], 'The other JavaScript setting is set correctly.');
-  }
-
-  /**
-   * Tests adding an external JavaScript File.
-   */
-  function testAddExternal() {
-    $attached['#attached']['js']['http://example.com/script.js'] = array('type' => 'external');
-    $this->render($attached);
-    $javascript = _drupal_add_js();
-    $this->assertTrue(array_key_exists('http://example.com/script.js', $javascript), 'Added an external JavaScript file.');
-  }
-
-  /**
-   * Tests adding JavaScript files with additional attributes.
-   */
-  function testAttributes() {
-    $default_query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
-
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['http://example.com/script.js'] = array(
-      'type' => 'external',
-      'attributes' => array('defer' => 'defer'),
-    );
-    $attached['#attached']['js']['core/misc/collapse.js'] = array(
-      'attributes' => array('defer' => 'defer'),
-    );
-    $this->render($attached);
-    $javascript = drupal_get_js();
-
-    $expected_1 = '<script src="http://example.com/script.js" 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.');
-  }
-
-  /**
-   * Tests that attributes are maintained when JS aggregation is enabled.
-   */
-  function testAggregatedAttributes() {
-    // Enable aggregation.
-    \Drupal::config('system.performance')->set('js.preprocess', 1)->save();
-
-    $default_query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
-
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['http://example.com/script.js'] = array(
-      'type' => 'external',
-      'attributes' => array('defer' => 'defer'),
-    );
-    $attached['#attached']['js']['core/misc/collapse.js'] = array(
-      'attributes' => array('defer' => 'defer'),
-    );
-    $this->render($attached);
-    $javascript = drupal_get_js();
-
-    $expected_1 = '<script src="http://example.com/script.js" 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 with aggregation enabled.');
-    $this->assertTrue(strpos($javascript, $expected_2) > 0, 'Rendered internal JavaScript with correct defer attribute with aggregation enabled.');
-  }
-
-  /**
-   * Tests drupal_get_js() for JavaScript settings.
-   */
-  function testHeaderSetting() {
-    $attached = array();
-    $attached['#attached']['library'][] = 'core/drupalSettings';
-    // Nonsensical value to verify if it's possible to override path settings.
-    $attached['#attached']['drupalSettings']['path']['pathPrefix'] = 'yarhar';
-    $this->render($attached);
-
-    $javascript = drupal_get_js('header');
-
-    // Parse the generated drupalSettings <script> back to a PHP representation.
-    $startToken = 'drupalSettings = ';
-    $endToken = '}';
-    $start = strpos($javascript, $startToken) + strlen($startToken);
-    $end = strrpos($javascript, $endToken);
-    $json  = Unicode::substr($javascript, $start, $end - $start + 1);
-    $parsed_settings = Json::decode($json);
-
-    // Test whether the settings for core/drupalSettings are available.
-    $this->assertTrue(isset($parsed_settings['path']['baseUrl']), 'drupalSettings.path.baseUrl is present.');
-    $this->assertTrue(isset($parsed_settings['path']['scriptPath']), 'drupalSettings.path.scriptPath is present.');
-    $this->assertIdentical($parsed_settings['path']['pathPrefix'], 'yarhar', 'drupalSettings.path.pathPrefix is present and has the correct (overridden) value.');
-    $this->assertIdentical($parsed_settings['path']['currentPath'], '', 'drupalSettings.path.currentPath is present and has the correct value.');
-    $this->assertIdentical($parsed_settings['path']['currentPathIsAdmin'], FALSE, 'drupalSettings.path.currentPathIsAdmin is present and has the correct value.');
-    $this->assertIdentical($parsed_settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is present and has the correct value.');
-    $this->assertIdentical($parsed_settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is present and has the correct value.');
-
-    // Tests whether altering JavaScript settings via hook_js_settings_alter()
-    // is working as expected.
-    // @see common_test_js_settings_alter()
-    $this->assertIdentical($parsed_settings['locale']['pluralDelimiter'], '☃');
-    $this->assertIdentical($parsed_settings['foo'], 'bar');
-  }
-
-  /**
-   * Tests to see if resetting the JavaScript empties the cache.
-   */
-  function testReset() {
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['core/misc/collapse.js'] = array();
-    $this->render($attached);
-    drupal_static_reset('_drupal_add_js');
-    $this->assertEqual(array(), _drupal_add_js(), 'Resetting the JavaScript correctly empties the cache.');
-  }
-
-  /**
-   * Tests adding inline scripts.
-   */
-  function testAddInline() {
-    $inline = 'jQuery(function () { });';
-    $attached['#attached']['library'][] = 'core/jquery';
-    $attached['#attached']['js'][] = array(
-      'type' => 'inline',
-      'data' => $inline,
-      'attributes' => array('defer' => 'defer'),
-    );
-    $this->render($attached);
-    $javascript = _drupal_add_js();
-    $this->assertTrue(array_key_exists('core/assets/vendor/jquery/jquery.js', $javascript), 'jQuery is added when inline scripts are added.');
-    $data = end($javascript);
-    $this->assertEqual($inline, $data['data'], 'Inline JavaScript is correctly added to the footer.');
-  }
-
-  /**
-   * Tests rendering an external JavaScript file.
-   */
-  function testRenderExternal() {
-    $external = 'http://example.com/example.js';
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js'][] = array(
-      'type' => 'external',
-      'data' => $external,
-    );
-    $this->render($attached);
-
-    $javascript = drupal_get_js();
-    // Local files have a base_path() prefix, external files should not.
-    $this->assertTrue(strpos($javascript, 'src="' . $external) > 0, 'Rendering an external JavaScript file.');
-  }
-
-  /**
-   * Tests drupal_get_js() with a footer scope.
-   */
-  function testFooterHTML() {
-    $inline = 'jQuery(function () { });';
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js'][] = array(
-      'type' => 'inline',
-      'data' => $inline,
-      'scope' => 'footer',
-      'attributes' => array('defer' => 'defer'),
-    );
-    $this->render($attached);
-
-    $javascript = drupal_get_js('footer');
-    $this->assertTrue(strpos($javascript, $inline) > 0, 'Rendered JavaScript footer returns the inline code.');
-  }
-
-  /**
-   * Tests _drupal_add_js() sets preproccess to FALSE when cache is also FALSE.
-   */
-  function testNoCache() {
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['core/misc/collapse.js'] = array('cache' => FALSE);
-    $this->render($attached);
-    $javascript = _drupal_add_js();
-    $this->assertFalse($javascript['core/misc/collapse.js']['preprocess'], 'Setting cache to FALSE sets proprocess to FALSE when adding JavaScript.');
-  }
-
-  /**
-   * Tests adding a JavaScript file with a different group.
-   */
-  function testDifferentGroup() {
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['core/misc/collapse.js'] = array('group' => JS_THEME);
-    $this->render($attached);
-    $javascript = _drupal_add_js();
-    $this->assertEqual($javascript['core/misc/collapse.js']['group'], JS_THEME, 'Adding a JavaScript file with a different group caches the given group.');
-  }
-
-  /**
-   * Tests adding a JavaScript file with a different weight.
-   */
-  function testDifferentWeight() {
-    $attached['#attached']['js']['core/misc/collapse.js'] = array('weight' => 2);
-    $this->render($attached);
-    $javascript = _drupal_add_js();
-    $this->assertEqual($javascript['core/misc/collapse.js']['weight'], 2, 'Adding a JavaScript file with a different weight caches the given weight.');
-  }
-
-  /**
-   * Tests adding JavaScript within conditional comments.
-   *
-   * @see \Drupal\Core\Render\Element\HtmlTag::preRenderConditionalComments()
-   */
-  function testBrowserConditionalComments() {
-    $default_query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
-
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['core/misc/collapse.js'] = array(
-      'browsers' => array('IE' => 'lte IE 8', '!IE' => FALSE),
-    );
-    $attached['#attached']['js'][] = array(
-      'type' => 'inline',
-      'data' => 'jQuery(function () { });',
-      'browsers' => array('IE' => FALSE),
-    );
-    $this->render($attached);
-    $javascript = drupal_get_js();
-
-    $expected_1 = "<!--[if lte IE 8]>\n" . '<script src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '"></script>' . "\n<![endif]-->";
-    $expected_2 = "<!--[if !IE]><!-->\n" . '<script>' . "\n<!--//--><![CDATA[//><!--\n" . 'jQuery(function () { });' . "\n//--><!]]>\n" . '</script>' . "\n<!--<![endif]-->";
-
-    $this->assertTrue(strpos($javascript, $expected_1) > 0, 'Rendered JavaScript within downlevel-hidden conditional comments.');
-    $this->assertTrue(strpos($javascript, $expected_2) > 0, 'Rendered JavaScript within downlevel-revealed conditional comments.');
-  }
-
-  /**
-   * Tests JavaScript versioning.
-   */
-  function testVersionQueryString() {
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['core/misc/collapse.js'] = array('version' => 'foo');
-    $attached['#attached']['js']['core/misc/ajax.js'] = array('version' => 'bar');
-    $this->render($attached);
-    $javascript = drupal_get_js();
-    $this->assertTrue(strpos($javascript, 'core/misc/collapse.js?v=foo') > 0 && strpos($javascript, 'core/misc/ajax.js?v=bar') > 0 , 'JavaScript version identifiers correctly appended to URLs');
-  }
-
-  /**
-   * Tests JavaScript grouping and aggregation.
-   */
-  function testAggregation() {
-    $default_query_string = $this->container->get('state')->get('system.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.
-    $attached = array();
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['core/misc/ajax.js'] = array();
-    $attached['#attached']['js']['core/misc/collapse.js'] = array('every_page' => TRUE);
-    $attached['#attached']['js']['core/misc/autocomplete.js'] = array();
-    $attached['#attached']['js']['core/misc/batch.js'] = array('every_page' => TRUE);
-    $this->render($attached);
-    $javascript = drupal_get_js();
-    $expected = implode("\n", array(
-      '<script src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '"></script>',
-      '<script src="' . file_create_url('core/misc/batch.js') . '?' . $default_query_string . '"></script>',
-      '<script src="' . file_create_url('core/misc/ajax.js') . '?' . $default_query_string . '"></script>',
-      '<script src="' . file_create_url('core/misc/autocomplete.js') . '?' . $default_query_string . '"></script>',
-    ));
-    $this->assertTrue(strpos($javascript, $expected) > 0, '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');
-    $config = \Drupal::config('system.performance');
-    $config->set('js.preprocess', 1);
-    $config->save();
-    $attached = array();
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['core/misc/ajax.js'] = array();
-    $attached['#attached']['js']['core/misc/collapse.js'] = array('every_page' => TRUE);
-    $attached['#attached']['js']['core/misc/autocomplete.js'] = array();
-    $attached['#attached']['js']['core/misc/batch.js'] = array('every_page' => TRUE);
-    $this->render($attached);
-    $js_items = _drupal_add_js();
-    $javascript = drupal_get_js();
-    $expected = implode("\n", array(
-      '<script src="' . $this->calculateAggregateFilename(array('core/misc/collapse.js' => $js_items['core/misc/collapse.js'], 'core/misc/batch.js' => $js_items['core/misc/batch.js'])) . '"></script>',
-      '<script src="' . $this->calculateAggregateFilename(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) !== FALSE, 'JavaScript is aggregated in the expected groups and order.');
-  }
-
-  /**
-   * Tests JavaScript aggregation when files are added to a different scope.
-   */
-  function testAggregationOrder() {
-    // Enable JavaScript aggregation.
-    \Drupal::config('system.performance')->set('js.preprocess', 1)->save();
-    drupal_static_reset('_drupal_add_js');
-
-    // Add two JavaScript files to the current request and build the cache.
-    $attached = array();
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['core/misc/ajax.js'] = array();
-    $attached['#attached']['js']['core/misc/autocomplete.js'] = array();
-    $this->render($attached);
-
-    $js_items = _drupal_add_js();
-    $scripts_html = array(
-      '#type' => 'scripts',
-      '#items' => array(
-        'core/misc/ajax.js' => $js_items['core/misc/ajax.js'],
-        'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js']
-      )
-    );
-    $this->render($scripts_html);
-
-    // Store the expected key for the first item in the cache.
-    $cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array());
-    $expected_key = $cache[0];
-
-    // Reset variables and add a file in a different scope first.
-    \Drupal::state()->delete('system.js_cache_files');
-    drupal_static_reset('_drupal_add_js');
-    $attached = array();
-    $attached['#attached']['library'][] = 'core/drupal';
-    $attached['#attached']['js']['some/custom/javascript_file.js'] = array('scope' => 'footer');
-    $attached['#attached']['js']['core/misc/ajax.js'] = array();
-    $attached['#attached']['js']['core/misc/autocomplete.js'] = array();
-    $this->render($attached);
-
-    // Rebuild the cache.
-    $js_items = _drupal_add_js();
-    $scripts_html = array(
-      '#type' => 'scripts',
-      '#items' => array(
-        'core/misc/ajax.js' => $js_items['core/misc/ajax.js'],
-        'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js']
-      )
-    );
-    $this->render($scripts_html);
-
-    // Compare the expected key for the first file to the current one.
-    $cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array());
-    $key = $cache[0];
-    $this->assertEqual($key, $expected_key, 'JavaScript aggregation is not affected by ordering in different scopes.');
-  }
-
-  /**
-   * Tests JavaScript ordering.
-   */
-  function testRenderOrder() {
-    $shared_options = array(
-      'type' => 'inline',
-      'scope' => 'footer',
-    );
-    // Add a bunch of JavaScript in strange ordering.
-    $attached['#attached']['js'][] = $shared_options + array(
-      'data' => '(function($){alert("Weight 5 #1");})(jQuery);',
-      'weight' => 5,
-    );
-    $attached['#attached']['js'][] = $shared_options + array(
-      'data' => '(function($){alert("Weight 0 #1");})(jQuery);',
-    );
-    $attached['#attached']['js'][] = $shared_options + array(
-      'data' => '(function($){alert("Weight 0 #2");})(jQuery);',
-    );
-    $attached['#attached']['js'][] = $shared_options + array(
-      'data' => '(function($){alert("Weight -8 #1");})(jQuery);',
-      'weight' => -8,
-    );
-    $attached['#attached']['js'][] = $shared_options + array(
-      'data' => '(function($){alert("Weight -8 #2");})(jQuery);',
-      'weight' => -8,
-    );
-    $attached['#attached']['js'][] = $shared_options + array(
-      'data' => '(function($){alert("Weight -8 #3");})(jQuery);',
-      'weight' => -8,
-    );
-    $attached['#attached']['js']['http://example.com/example.js?Weight -5 #1'] = array(
-      'type' => 'external',
-      'scope' => 'footer',
-      'weight' => -5,
-    );
-    $attached['#attached']['js'][] = $shared_options + array(
-      'data' => '(function($){alert("Weight -8 #4");})(jQuery);',
-      'weight' => -8,
-    );
-    $attached['#attached']['js'][] = $shared_options + array(
-      'data' => '(function($){alert("Weight 5 #2");})(jQuery);',
-      'weight' => 5,
-    );
-    $attached['#attached']['js'][] = $shared_options + array(
-      'data' => '(function($){alert("Weight 0 #3");})(jQuery);',
-    );
-    $this->render($attached);
-
-    // Construct the expected result from the regex.
-    $expected = array(
-      "-8 #1",
-      "-8 #2",
-      "-8 #3",
-      "-8 #4",
-      "-5 #1", // The external script.
-      "0 #1",
-      "0 #2",
-      "0 #3",
-      "5 #1",
-      "5 #2",
-    );
-
-    // Retrieve the rendered JavaScript and test against the regex.
-    $js = drupal_get_js('footer');
-    $matches = array();
-    if (preg_match_all('/Weight\s([-0-9]+\s[#0-9]+)/', $js, $matches)) {
-      $result = $matches[1];
-    }
-    else {
-      $result = array();
-    }
-    $this->assertIdentical($result, $expected, 'JavaScript is added in the expected weight order.');
-  }
-
-  /**
-   * Tests rendering the JavaScript with a file's weight above jQuery's.
-   */
-  function testRenderDifferentWeight() {
-    // JavaScript files are sorted first by group, then by the 'every_page'
-    // flag, then by weight (see drupal_sort_css_js()), so to test the effect of
-    // weight, we need the other two options to be the same.
-    $attached['#attached']['library'][] = 'core/jquery';
-    $attached['#attached']['js']['core/misc/collapse.js'] = array(
-      'group' => JS_LIBRARY,
-      'every_page' => TRUE,
-      'weight' => -21,
-    );
-    $this->render($attached);
-    $javascript = drupal_get_js();
-    $this->assertTrue(strpos($javascript, 'core/misc/collapse.js') < strpos($javascript, 'core/assets/vendor/jquery/jquery.js'), 'Rendering a JavaScript file above jQuery.');
-  }
-
-  /**
-   * Tests altering a JavaScript's weight via hook_js_alter().
-   *
-   * @see simpletest_js_alter()
-   */
-  function testAlter() {
-    // Add both tableselect.js and simpletest.js, with a larger weight on SimpleTest.
-    $attached['#attached']['js']['core/misc/tableselect.js'] = array();
-    $attached['#attached']['js'][drupal_get_path('module', 'simpletest') . '/simpletest.js'] = array('weight' => 9999);
-    $this->render($attached);
-
-    // Render the JavaScript, testing if simpletest.js was altered to be before
-    // tableselect.js. See simpletest_js_alter() to see where this alteration
-    // takes place.
-    $javascript = drupal_get_js();
-    $this->assertTrue(strpos($javascript, 'simpletest.js') < strpos($javascript, 'core/misc/tableselect.js'), 'Altering JavaScript weight through the alter hook.');
-  }
-
-  /**
-   * Adds a library to the page and tests for both its JavaScript and its CSS.
-   */
-  function testLibraryRender() {
-    $attached = array();
-    $attached['#attached']['library'][] = 'core/jquery.farbtastic';
-    $this->render($attached);
-    $scripts = drupal_get_js();
-    $styles = drupal_get_css();
-    $this->assertTrue(strpos($scripts, 'core/assets/vendor/farbtastic/farbtastic.js'), 'JavaScript of library was added to the page.');
-    $this->assertTrue(strpos($styles, 'core/assets/vendor/farbtastic/farbtastic.css'), 'Stylesheet of library was added to the page.');
-  }
-
-  /**
-   * Adds a JavaScript library to the page and alters it.
-   *
-   * @see common_test_library_info_alter()
-   */
-  function testLibraryAlter() {
-    // Verify that common_test altered the title of Farbtastic.
-    /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
-    $library_discovery = \Drupal::service('library.discovery');
-    $library = $library_discovery->getLibraryByName('core', 'jquery.farbtastic');
-    $this->assertEqual($library['version'], '0.0', 'Registered libraries were altered.');
-
-    // common_test_library_info_alter() also added a dependency on jQuery Form.
-    $attached['#attached']['library'][] = 'core/jquery.farbtastic';
-    $this->render($attached);
-    $scripts = drupal_get_js();
-    $this->assertTrue(strpos($scripts, 'core/assets/vendor/jquery-form/jquery.form.js'), 'Altered library dependencies are added to the page.');
-  }
-
-  /**
-   * Tests that multiple modules can implement the same library.
-   *
-   * @see common_test.library.yml
-   */
-  function testLibraryNameConflicts() {
-    /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
-    $library_discovery = \Drupal::service('library.discovery');
-    $farbtastic = $library_discovery->getLibraryByName('common_test', 'jquery.farbtastic');
-    $this->assertEqual($farbtastic['version'], '0.1', 'Alternative libraries can be added to the page.');
-  }
-
-  /**
-   * Tests non-existing libraries.
-   */
-  function testLibraryUnknown() {
-    /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
-    $library_discovery = \Drupal::service('library.discovery');
-    $result = $library_discovery->getLibraryByName('unknown', 'unknown');
-    $this->assertFalse($result, 'Unknown library returned FALSE.');
-    drupal_static_reset('drupal_get_library');
-
-    $attached['#attached']['library'][] = 'unknown/unknown';
-    $this->render($attached);
-    $scripts = drupal_get_js();
-    $this->assertTrue(strpos($scripts, 'unknown') === FALSE, 'Unknown library was not added to the page.');
-  }
-
-  /**
-   * Tests the addition of libraries through the #attached['library'] property.
-   */
-  function testAttachedLibrary() {
-    $element['#attached']['library'][] = 'core/jquery.farbtastic';
-    $this->render($element);
-    $scripts = drupal_get_js();
-    $this->assertTrue(strpos($scripts, 'core/assets/vendor/farbtastic/farbtastic.js'), 'The attached_library property adds the additional libraries.');
-  }
-
-  /**
-   * Tests retrieval of libraries via drupal_get_library().
-   */
-  function testGetLibrary() {
-    /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
-    $library_discovery = \Drupal::service('library.discovery');
-    // Retrieve all libraries registered by a module.
-    $libraries = $library_discovery->getLibrariesByExtension('common_test');
-    $this->assertTrue(isset($libraries['jquery.farbtastic']), 'Retrieved all module libraries.');
-    // Retrieve all libraries for a module not declaring any libraries.
-    // Note: This test installs language module.
-    $libraries = $library_discovery->getLibrariesByExtension('dblog');
-    $this->assertEqual($libraries, array(), 'Retrieving libraries from a module not declaring any libraries returns an emtpy array.');
-
-    // Retrieve a specific library by module and name.
-    $farbtastic = $library_discovery->getLibraryByName('common_test', 'jquery.farbtastic');
-    $this->assertEqual($farbtastic['version'], '0.1', 'Retrieved a single library.');
-    // Retrieve a non-existing library by module and name.
-    $farbtastic = $library_discovery->getLibraryByName('common_test', 'foo');
-    $this->assertIdentical($farbtastic, FALSE, 'Retrieving a non-existing library returns FALSE.');
-  }
-
-  /**
-   * Tests JavaScript files that have querystrings attached get added right.
-   */
-  function testAddJsFileWithQueryString() {
-    $js = drupal_get_path('module', 'node') . '/node.js';
-    _drupal_add_js($js);
-
-    $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
-    $scripts = drupal_get_js();
-    $this->assertTrue(strpos($scripts, $js . '?' . $query_string), 'Query string was appended correctly to JS.');
-  }
-
-  /**
-   * Calculates the aggregated file URI of a group of JavaScript assets.
-   *
-   * @param array $js_assets
-   *   A group of JavaScript assets.
-   * @return string
-   *   A file URI.
-   *
-   * @see testAggregation()
-   * @see testAggregationOrder()
-   */
-  protected function calculateAggregateFilename($js_assets) {
-    $data = '';
-    foreach ($js_assets as $js_asset) {
-      $data .= file_get_contents($js_asset['data']) . ";\n";
-    }
-    return file_create_url('public://js/js_' . Crypt::hashBase64($data) . '.js');
-  }
-
-}
diff --git a/core/modules/system/src/Tests/Common/MergeAttachmentsTest.php b/core/modules/system/src/Tests/Common/MergeAttachmentsTest.php
index 20a4401..f59e395 100644
--- a/core/modules/system/src/Tests/Common/MergeAttachmentsTest.php
+++ b/core/modules/system/src/Tests/Common/MergeAttachmentsTest.php
@@ -67,100 +67,6 @@ function testLibraryMerging() {
   }
 
   /**
-   * Tests justs CSS asset merging.
-   */
-  function testCssMerging() {
-    $a['#attached'] = array(
-      'css' => array(
-        'foo.css' => array(),
-        'bar.css' => array(),
-      ),
-    );
-    $b['#attached'] = array(
-      'css' => array(
-        'baz.css' => array(),
-      ),
-    );
-    $expected['#attached'] = array(
-      'css' => array(
-        'foo.css' => array(),
-        'bar.css' => array(),
-        'baz.css' => array(),
-      ),
-    );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
-
-    // Merging in the opposite direction yields the opposite CSS asset order.
-    $expected['#attached'] = array(
-      'css' => array(
-        'baz.css' => array(),
-        'foo.css' => array(),
-        'bar.css' => array(),
-      ),
-    );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
-
-    // Merging with duplicates: duplicates are automatically removed because the
-    // values have unique keys.
-    $b['#attached']['css']['bar.css'] = array();
-    $expected['#attached'] = array(
-      'css' => array(
-        'foo.css' => array(),
-        'bar.css' => array(),
-        'baz.css' => array(),
-      ),
-    );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; CSS asset duplicates removed.');
-  }
-
-  /**
-   * Tests justs JavaScript asset merging.
-   */
-  function testJsMerging() {
-    $a['#attached'] = array(
-      'js' => array(
-        'foo.js' => array(),
-        'bar.js' => array(),
-      ),
-    );
-    $b['#attached'] = array(
-      'js' => array(
-        'baz.js' => array(),
-      ),
-    );
-    $expected['#attached'] = array(
-      'js' => array(
-        'foo.js' => array(),
-        'bar.js' => array(),
-        'baz.js' => array(),
-      ),
-    );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
-
-    // Merging in the opposite direction yields the opposite JS asset order.
-    $expected['#attached'] = array(
-      'js' => array(
-        'baz.js' => array(),
-        'foo.js' => array(),
-        'bar.js' => array(),
-      ),
-    );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
-
-    // Merging with duplicates: duplicates are automatically removed because the
-    // values have unique keys.
-    $b['#attached']['js']['bar.js'] = array();
-    $expected['#attached'] = array(
-      'js' => array(
-        'foo.js' => array(),
-        'bar.js' => array(),
-        'baz.js' => array(),
-      ),
-    );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; JS asset duplicates removed.');
-  }
-
-  /**
    * Tests justs JavaScript and JavaScript setting asset merging.
    */
   function testJsSettingMerging() {
diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php
index a1d7523..6848dc9 100644
--- a/core/modules/system/src/Tests/Common/RenderTest.php
+++ b/core/modules/system/src/Tests/Common/RenderTest.php
@@ -354,40 +354,35 @@ function testDrupalRenderChildrenAttached() {
     \Drupal::request()->setMethod('GET');
 
     // Create an element with a child and subchild. Each element loads a
-    // different JavaScript file using #attached.
-    $parent_js = drupal_get_path('module', 'user') . '/user.js';
-    $child_js = drupal_get_path('module', 'forum') . '/forum.js';
-    $subchild_js = drupal_get_path('module', 'book') . '/book.js';
+    // different library using #attached.
     $element = array(
-      '#type' => 'details',
-      '#open' => TRUE,
+      '#type' => 'container',
       '#cache' => array(
         'keys' => array('simpletest', 'drupal_render', 'children_attached'),
       ),
-      '#attached' => array('js' => array($parent_js)),
+      '#attached' => ['library' => ['test/parent']],
       '#title' => 'Parent',
     );
     $element['child'] = array(
-      '#type' => 'details',
-      '#open' => TRUE,
-      '#attached' => array('js' => array($child_js)),
+      '#type' => 'container',
+      '#attached' => ['library' => ['test/child']],
       '#title' => 'Child',
     );
     $element['child']['subchild'] = array(
-      '#attached' => array('js' => array($subchild_js)),
+      '#attached' => ['library' => ['test/subchild']],
       '#markup' => 'Subchild',
     );
 
     // Render the element and verify the presence of #attached JavaScript.
     drupal_render($element);
-    $expected_js = [$parent_js, $child_js, $subchild_js];
-    $this->assertEqual($element['#attached']['js'], $expected_js, 'The element, child and subchild #attached JavaScript are included.');
+    $expected_libraries = ['test/parent', 'test/child', 'test/subchild'];
+    $this->assertEqual($element['#attached']['library'], $expected_libraries, 'The element, child and subchild #attached libraries are included.');
 
     // Load the element from cache and verify the presence of the #attached
     // JavaScript.
     $element = array('#cache' => array('keys' => array('simpletest', 'drupal_render', 'children_attached')));
     $this->assertTrue(strlen(drupal_render($element)) > 0, 'The element was retrieved from cache.');
-    $this->assertEqual($element['#attached']['js'], $expected_js, 'The element, child and subchild #attached JavaScript are included.');
+    $this->assertEqual($element['#attached']['library'], $expected_libraries, 'The element, child and subchild #attached libraries are included.');
 
     // Restore the previous request method.
     \Drupal::request()->setMethod($request_method);
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.libraries.yml b/core/modules/system/tests/modules/ajax_test/ajax_test.libraries.yml
new file mode 100644
index 0000000..5f94748
--- /dev/null
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.libraries.yml
@@ -0,0 +1,20 @@
+order:
+ drupalSettings:
+   ajax: test
+ dependencies:
+   - ajax_test/order-css-command
+   - ajax_test/order-js-command
+
+order-css-command:
+  css:
+     theme:
+       # Two CSS files (order should remain the same).
+       a.css: {}
+       b.css: {}
+
+order-js-command:
+  js:
+   # Two JavaScript files (first to the footer, should appear last).
+   footer.js: { scope: footer }
+   header.js: {}
+
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.module b/core/modules/system/tests/modules/ajax_test/ajax_test.module
index 1f19aac..d81da66 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.module
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.module
@@ -47,29 +47,10 @@ function ajax_test_render() {
  */
 function ajax_test_order() {
   $response = new AjaxResponse();
-  $path = drupal_get_path('module', 'system');
   // HTML insertion command.
   $response->addCommand(new HtmlCommand('body', 'Hello, world!'));
-  $attached = array(
-    '#attached' => array(
-      'css' => array(
-        // Add two CSS files (order should remain the same).
-        $path . '/css/system.admin.css' => array(),
-        $path . '/css/system.maintenance.css' => array(),
-      ),
-      'js' => array(
-        // Add two JavaScript files (first to the footer, should appear last).
-        $path . '/system.modules.js' => array('scope' => 'footer'),
-        $path . '/system.js' => array(),
-      ),
-      // Finally, add a JavaScript setting.
-      'drupalSettings' => array(
-        'ajax' => 'test',
-      ),
-    ),
-  );
-
-  drupal_process_attached($attached);
+  $build['#attached']['library'][] = 'ajax_test/order';
+  drupal_process_attached($build);
 
   return $response;
 }
diff --git a/core/modules/system/tests/modules/common_test/common_test.libraries.yml b/core/modules/system/tests/modules/common_test/common_test.libraries.yml
index 47d2b37..69b9c11 100644
--- a/core/modules/system/tests/modules/common_test/common_test.libraries.yml
+++ b/core/modules/system/tests/modules/common_test/common_test.libraries.yml
@@ -7,3 +7,106 @@ jquery.farbtastic:
       assets/vendor/farbtastic/farbtastic.css: {}
   dependencies:
     - core/jquery
+
+# Library to test CSS and JS file assets.
+files:
+  js:
+    foo.js: {}
+  css:
+    theme:
+      bar.css: {}
+
+# Library to test external CSS and JS file assets.
+external:
+  version: 1
+  js:
+    http://example.com/script.js: { type: external }
+  css:
+    theme:
+      http://example.com/stylesheet.css: { type: external }
+
+# Library to test JS file asset attributes (both internal and external).
+js-attributes:
+  version: 1
+  js:
+    deferred-internal.js: { attributes: { defer: true, bar: foo } }
+    http://example.com/deferred-external.js:
+      type: external
+      attributes:
+        foo: bar
+        defer: true
+
+js-footer:
+  js:
+    footer.js: { scope: footer }
+
+# Library to test setting cache = FALSE, to prevent aggregation.
+no-cache:
+  js:
+    nocache.js: { cache: false }
+
+order:
+  js:
+    weight_-3_1.js: { weight: -3 }
+    weight_0_1.js: {}
+    weight_0_2.js: {}
+    weight_-8_1.js: { weight: -8 }
+    weight_-8_2.js: { weight: -8 }
+    weight_-8_3.js: { weight: -8 }
+    http://example.com/weight_-5_1.js: { type: external, weight: -5 }
+    weight_-8_4.js: { weight: -8 }
+    weight_-3_2.js: { weight: -3 }
+    weight_0_3.js: {}
+  css:
+    base:
+      base_weight_0_1.js: {}
+      base_weight_0_2.js: {}
+      base_weight_-8_1.js: { weight: -8 }
+      base_weight_-101_1.js: { weight: -101 }
+    layout:
+      layout_weight_0_1.js: {}
+      layout_weight_0_2.js: {}
+      layout_weight_-8_1.js: { weight: -8 }
+      layout_weight_-101_1.js: { weight: -101 }
+    component:
+      component_weight_0_1.js: {}
+      component_weight_0_2.js: {}
+      component_weight_-8_1.js: { weight: -8}
+      component_weight_-101_1.js: { weight: -101}
+    state:
+      state_weight_0_1.js: {}
+      state_weight_0_2.js: {}
+      state_weight_-8_1.js: { weight: -8}
+      state_weight_-101_1.js: { weight: -101}
+    theme:
+      theme_weight_0_1.js: {}
+      theme_weight_0_2.js: {}
+      theme_weight_-8_1.js: { weight: -8}
+      theme_weight_-101_1.js: { weight: -101}
+
+weight:
+  css:
+    theme:
+      first.css: {}
+      lighter.js: { weight: -1 }
+  js:
+    first.js: {}
+    lighter.js: { weight: -1 }
+    before-jquery.js: { weight: -21 }
+
+browsers:
+  js:
+    old-ie.js:
+      browsers:
+        'IE': 'lte IE 8'
+        '!IE': false
+    no-ie.js:
+      browsers:
+        IE: false
+
+querystring:
+  js:
+    querystring.js?arg1=value1&arg2=value2: {}
+  css:
+    theme:
+      querystring.css?arg1=value1&arg2=value2: {}
diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php
index a335e38..24a4cf4 100644
--- a/core/modules/system/theme.api.php
+++ b/core/modules/system/theme.api.php
@@ -258,7 +258,7 @@
  *   normally preferable to use #theme or #type instead, so that the theme can
  *   customize the markup.
  *
- * JavaScript and CSS attributes are specified in the render array using the
+ * JavaScript and CSS assets are specified in the render array using the
  * #attached property (see @ref sec_attached).
  *
  * @section elements Render elements
@@ -286,18 +286,22 @@
  *
  * @section sec_attached Attaching libraries in render arrays
  *
- * The #attached property allows loading of CSS, JavaScript, libraries or custom
- * types. Specify an array of type => value pairs, where the type (most often
- * 'css', 'js', or 'library') determines the loading technique and the value
- * provides the options specified to the loader function. Example:
- * @code
- * $form['#attached']['css'] = array(
- *   drupal_get_path('module', 'ajax_example') . '/ajax_example.css',
- * );
  *
- * $form['#attached']['js'] = array(
- *  drupal_get_path('module', 'ajax_example') . '/ajax_example.js',
- * );
+ * Libraries, JavaScript settings, feeds, HTML <head> tags and HTML <head> links
+ * are attached to elements using the #attached property. The #attached property
+ * is an associative array, where the keys are the the attachment types and the
+ * values are the attached data. For example:
+ *
+ * The #attached property allows loading of asset libraries (which may contain
+ * CSS assets, JavaScript assets, and JavaScript setting assets), JavaScript
+ * settings, feeds, HTML <head> tags and HTML <head> links. Specify an array of
+ * type => value pairs, where the type (most often 'library' — for libraries, or
+ * 'drupalSettings' — for JavaScript settings) to attach these response-level
+ * values. Example:
+ * @code
+ * $build['#attached']['library'][] = 'core/jquery';
+ * $build['#attached']['drupalSettings']['foo] = 'bar';
+ * $build['#attached']['feed'][] = ['aggregator/rss', $this->t('Feed title')];
  * @endcode
  *
  * See drupal_process_attached() for additional information.
diff --git a/core/modules/views/src/Tests/Plugin/CacheTest.php b/core/modules/views/src/Tests/Plugin/CacheTest.php
index 6f669d2..7f9e460 100644
--- a/core/modules/views/src/Tests/Plugin/CacheTest.php
+++ b/core/modules/views/src/Tests/Plugin/CacheTest.php
@@ -148,10 +148,7 @@ function testHeaderStorage() {
     $output = $view->preview();
     drupal_render($output);
     $this->assertTrue(in_array('views_test_data/test', $output['#attached']['library']), 'Make sure libraries are added for cached views.');
-    $css_path = drupal_get_path('module', 'views_test_data') . '/views_cache.test.css';
-    $js_path = drupal_get_path('module', 'views_test_data') . '/views_cache.test.js';
-    $this->assertTrue(in_array($css_path, $output['#attached']['css']), 'Make sure the css is added for cached views.');
-    $this->assertTrue(in_array($js_path, $output['#attached']['js']), 'Make sure the js is added for cached views.');
+    $this->assertEqual(['foo' => 'bar'], $output['#attached']['drupalSettings'], 'Make sure drupalSettings are added for cached views.');
     $this->assertTrue(['views_test_data:1'], $output['#cache']['tags']);
     $this->assertTrue(['views_test_data_post_render_cache' => [['foo' => 'bar']]], $output['#post_render_cache']);
     $this->assertFalse(!empty($view->build_info['pre_render_called']), 'Make sure hook_views_pre_render is not called for the cached view.');
diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php
index 9214808..8c61f35 100644
--- a/core/modules/views/src/ViewExecutable.php
+++ b/core/modules/views/src/ViewExecutable.php
@@ -388,20 +388,20 @@ class ViewExecutable {
   /**
    * A render array container to store render related information.
    *
-   * For example you can alter the array and attach some css/js via the
-   * #attached key. This is the required way to add custom css/js.
+   * For example you can alter the array and attach some asset library or JS
+   * settings via the #attached key. This is the required way to add custom
+   * CSS or JS.
    *
    * @var array
    *
    * @see drupal_process_attached
    */
-  public $element = array(
-    '#attached' => array(
-      'css' => array(),
-      'js' => array(),
-      'library' => array(),
-    ),
-  );
+  public $element = [
+    '#attached' => [
+      'library' => [],
+      'drupalSettings' => [],
+    ]
+  ];
 
   /**
    * The current user.
diff --git a/core/modules/views/tests/modules/views_test_data/views_test_data.views_execution.inc b/core/modules/views/tests/modules/views_test_data/views_test_data.views_execution.inc
index 8f0df18..d3ceb3b 100644
--- a/core/modules/views/tests/modules/views_test_data/views_test_data.views_execution.inc
+++ b/core/modules/views/tests/modules/views_test_data/views_test_data.views_execution.inc
@@ -47,11 +47,8 @@ function views_test_data_views_pre_render(ViewExecutable $view) {
   \Drupal::state()->set('views_hook_test_views_pre_render', TRUE);
 
   if (isset($view) && ($view->storage->id() == 'test_cache_header_storage')) {
-    $path = drupal_get_path('module', 'views_test_data');
     $view->element['#attached']['library'][] = 'views_test_data/test';
     $view->element['#attached']['drupalSettings']['foo'] = 'bar';
-    $view->element['#attached']['js'][] = "$path/views_cache.test.js";
-    $view->element['#attached']['css'][] = "$path/views_cache.test.css";
     $view->element['#cache']['tags'][] = 'views_test_data:1';
     $view->element['#post_render_cache']['views_test_data_post_render_cache'][] = ['foo' => 'bar'];
     $view->build_info['pre_render_called'] = TRUE;
diff --git a/core/tests/Drupal/Tests/Core/Asset/CssCollectionGrouperUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssCollectionGrouperUnitTest.php
index 8ca43ae..688ba54 100644
--- a/core/tests/Drupal/Tests/Core/Asset/CssCollectionGrouperUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionGrouperUnitTest.php
@@ -69,26 +69,6 @@ function testGrouper() {
         'browsers' => array('IE' => TRUE, '!IE' => TRUE),
         'basename' => 'jquery.ui.core.css',
       ),
-      0 => array(
-        'type' => 'inline',
-        'group' => 0,
-        'weight' => 0.007,
-        'every_page' => FALSE,
-        'media' => 'all',
-        'preprocess' => TRUE,
-        'data' => 'body { padding: 0px; }',
-        'browsers' => array('IE' => TRUE, '!IE' => TRUE),
-      ),
-      1 => array(
-        'type' => 'inline',
-        'group' => 0,
-        'weight' => 0.007,
-        'every_page' => FALSE,
-        'media' => 'all',
-        'preprocess' => FALSE,
-        'data' => 'body { margin: 0px; }',
-        'browsers' => array('IE' => TRUE, '!IE' => TRUE),
-      ),
       'field.css' => array(
         'every_page' => TRUE,
         'group' => 0,
@@ -137,7 +117,7 @@ function testGrouper() {
 
     $groups = $this->grouper->group($css_assets);
 
-    $this->assertSame(count($groups), 7, "7 groups created.");
+    $this->assertSame(count($groups), 6, "6 groups created.");
 
     // Check group 1.
     $this->assertSame($groups[0]['group'], -100);
@@ -160,49 +140,39 @@ function testGrouper() {
 
     // Check group 3.
     $this->assertSame($groups[2]['group'], 0);
-    $this->assertSame($groups[2]['every_page'], FALSE);
-    $this->assertSame($groups[2]['type'], 'inline');
+    $this->assertSame($groups[2]['every_page'], TRUE);
+    $this->assertSame($groups[2]['type'], 'file');
     $this->assertSame($groups[2]['media'], 'all');
     $this->assertSame($groups[2]['preprocess'], TRUE);
-    $this->assertSame(count($groups[2]['items']), 2);
-    $this->assertContains($css_assets[0], $groups[2]['items']);
-    $this->assertContains($css_assets[1], $groups[2]['items']);
+    $this->assertSame(count($groups[2]['items']), 1);
+    $this->assertContains($css_assets['field.css'], $groups[2]['items']);
 
     // Check group 4.
     $this->assertSame($groups[3]['group'], 0);
-    $this->assertSame($groups[3]['every_page'], TRUE);
-    $this->assertSame($groups[3]['type'], 'file');
+    $this->assertSame($groups[3]['every_page'], FALSE);
+    $this->assertSame($groups[3]['type'], 'external');
     $this->assertSame($groups[3]['media'], 'all');
     $this->assertSame($groups[3]['preprocess'], TRUE);
     $this->assertSame(count($groups[3]['items']), 1);
-    $this->assertContains($css_assets['field.css'], $groups[3]['items']);
+    $this->assertContains($css_assets['external.css'], $groups[3]['items']);
 
     // Check group 5.
-    $this->assertSame($groups[4]['group'], 0);
-    $this->assertSame($groups[4]['every_page'], FALSE);
-    $this->assertSame($groups[4]['type'], 'external');
+    $this->assertSame($groups[4]['group'], 100);
+    $this->assertSame($groups[4]['every_page'], TRUE);
+    $this->assertSame($groups[4]['type'], 'file');
     $this->assertSame($groups[4]['media'], 'all');
     $this->assertSame($groups[4]['preprocess'], TRUE);
     $this->assertSame(count($groups[4]['items']), 1);
-    $this->assertContains($css_assets['external.css'], $groups[4]['items']);
+    $this->assertContains($css_assets['style.css'], $groups[4]['items']);
 
     // Check group 6.
     $this->assertSame($groups[5]['group'], 100);
     $this->assertSame($groups[5]['every_page'], TRUE);
     $this->assertSame($groups[5]['type'], 'file');
-    $this->assertSame($groups[5]['media'], 'all');
+    $this->assertSame($groups[5]['media'], 'print');
     $this->assertSame($groups[5]['preprocess'], TRUE);
     $this->assertSame(count($groups[5]['items']), 1);
-    $this->assertContains($css_assets['style.css'], $groups[5]['items']);
-
-    // Check group 7.
-    $this->assertSame($groups[6]['group'], 100);
-    $this->assertSame($groups[6]['every_page'], TRUE);
-    $this->assertSame($groups[6]['type'], 'file');
-    $this->assertSame($groups[6]['media'], 'print');
-    $this->assertSame($groups[6]['preprocess'], TRUE);
-    $this->assertSame(count($groups[6]['items']), 1);
-    $this->assertContains($css_assets['print.css'], $groups[6]['items']);
+    $this->assertContains($css_assets['print.css'], $groups[5]['items']);
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php
index 5028f45..64a3833 100644
--- a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php
@@ -57,13 +57,6 @@ class CssCollectionRendererUnitTest extends UnitTestCase {
   protected $file_css_group;
 
   /**
-   * A valid inline CSS asset group.
-   *
-   * @var array
-   */
-  protected $inline_css_group;
-
-  /**
    * The state mock class.
    *
    * @var \Drupal\Core\State\StateInterface|\PHPUnit_Framework_MockObject_MockObject
@@ -108,36 +101,6 @@ protected function setUp() {
         ),
       ),
     );
-    $this->inline_css_group = array(
-      'group' => 0,
-      'every_page' => FALSE,
-      'type' => 'inline',
-      'media' => 'all',
-      'preprocess' => TRUE,
-      'browsers' => array('IE' => TRUE, '!IE' => TRUE),
-      'items' => array(
-        0 => array(
-          'group' => 0,
-          'every_page' => FALSE,
-          'type' => 'inline',
-          'weight' => 0.012,
-          'media' => 'all',
-          'preprocess' => TRUE,
-          'data' => '.girlfriend { display: none; }',
-          'browsers' => array('IE' => TRUE, '!IE' => TRUE),
-        ),
-        1 => array(
-          'group' => 0,
-          'every_page' => FALSE,
-          'type' => 'file',
-          'weight' => 0.013,
-          'media' => 'all',
-          'preprocess' => FALSE,
-          'data' => '#home body { position: fixed; }',
-          'browsers' => array('IE' => TRUE, '!IE' => TRUE),
-        ),
-      ),
-    );
   }
 
   /**
@@ -191,15 +154,6 @@ function providerTestRender() {
           0 => $create_link_element('http://example.com/popular.js', 'all'),
         ),
       ),
-      // Single inline CSS asset.
-      1 => array(
-        array(
-          0 => array('group' => 0, 'every_page' => FALSE, 'type' => 'inline', 'media' => 'all', 'preprocess' => FALSE, 'data' => '.girlfriend { display: none; }', 'browsers' => array()),
-        ),
-        array(
-          0 => $create_style_element('.girlfriend { display: none; }', 'all', array(), TRUE),
-        ),
-      ),
       // Single file CSS asset.
       2 => array(
         array(
diff --git a/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php
index df90beb..54cdebc 100644
--- a/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Asset/CssOptimizerUnitTest.php
@@ -136,38 +136,41 @@ function providerTestOptimize() {
         ),
         file_get_contents($path . 'comment_hacks.css.optimized.css'),
       ),
-      // Inline. Preprocessing enabled.
-      3 => array(
+      // File in subfolder. Tests:
+      // - CSS import path is properly interpreted. (https://drupal.org/node/1198904)
+      // - Don't adjust data URIs (https://drupal.org/node/2142441)
+      5 => array(
         array(
-          'group' => 0,
-          'every_page' => FALSE,
-          'type' => 'inline',
-          'weight' => 0.012,
+          'group' => -100,
+          'every_page' => TRUE,
+          'type' => 'file',
+          'weight' => 0.013,
           'media' => 'all',
           'preprocess' => TRUE,
-          'data' => '.girlfriend { display: none; }',
+          'data' => $path . 'css_subfolder/css_input_with_import.css',
           'browsers' => array('IE' => TRUE, '!IE' => TRUE),
+          'basename' => 'css_input_with_import.css',
         ),
-        ".girlfriend{display:none;}\n",
+        str_replace('url(../images/icon.png)', 'url(' . file_create_url($path . 'images/icon.png') . ')', file_get_contents($path . 'css_subfolder/css_input_with_import.css.optimized.css')),
       ),
-      // Inline. Preprocessing disabled.
-      4 => array(
+      // File. Tests:
+      // - Any @charaset declaration at the beginning of a file should be
+      //   removed without breaking subsequent CSS.
+      6 => array(
         array(
-          'group' => 0,
-          'every_page' => FALSE,
-          'type' => 'inline',
+          'group' => -100,
+          'every_page' => TRUE,
+          'type' => 'file',
           'weight' => 0.013,
           'media' => 'all',
-          'preprocess' => FALSE,
-          'data' => '#home body { position: fixed; }',
+          'preprocess' => TRUE,
+          'data' => $path . 'charset_sameline.css',
           'browsers' => array('IE' => TRUE, '!IE' => TRUE),
+          'basename' => 'charset_sameline.css',
         ),
-        '#home body { position: fixed; }',
+        file_get_contents($path . 'charset.css.optimized.css'),
       ),
-      // File in subfolder. Tests:
-      // - CSS import path is properly interpreted. (https://drupal.org/node/1198904)
-      // - Don't adjust data URIs (https://drupal.org/node/2142441)
-      5 => array(
+      7 => array(
         array(
           'group' => -100,
           'every_page' => TRUE,
@@ -175,11 +178,11 @@ function providerTestOptimize() {
           'weight' => 0.013,
           'media' => 'all',
           'preprocess' => TRUE,
-          'data' => $path . 'css_subfolder/css_input_with_import.css',
+          'data' => $path . 'charset_newline.css',
           'browsers' => array('IE' => TRUE, '!IE' => TRUE),
-          'basename' => 'css_input_with_import.css',
+          'basename' => 'charset_newline.css',
         ),
-        str_replace('url(../images/icon.png)', 'url(' . file_create_url($path . 'images/icon.png') . ')', file_get_contents($path . 'css_subfolder/css_input_with_import.css.optimized.css')),
+        file_get_contents($path . 'charset.css.optimized.css'),
       ),
     );
   }
@@ -194,38 +197,6 @@ function testOptimize($css_asset, $expected) {
   }
 
   /**
-   * Tests optimizing a CSS asset containing charset declaration.
-   */
-  function testOptimizeRemoveCharset() {
-    $cases = array(
-      array(
-        'asset' => array(
-          'type' => 'inline',
-          'data' => '@charset "UTF-8";html{font-family:"sans-serif";}',
-          'preprocess' => FALSE,
-        ),
-        'expected' => 'html{font-family:"sans-serif";}',
-      ),
-      array(
-        // This asset contains extra \n character.
-        'asset' => array(
-          'type' => 'inline',
-          'data' => "@charset 'UTF-8';\nhtml{font-family:'sans-serif';}",
-          'preprocess' => FALSE,
-        ),
-        'expected' => "\nhtml{font-family:'sans-serif';}",
-      ),
-    );
-    foreach ($cases as $case) {
-      $this->assertEquals(
-        $case['expected'],
-        $this->optimizer->optimize($case['asset']),
-        'CSS optimizing correctly removes the charset declaration.'
-      );
-    }
-  }
-
-  /**
    * Tests a file CSS asset with preprocessing disabled.
    */
   function testTypeFilePreprocessingDisabled() {
diff --git a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php
index 141d432..18e8bbc 100644
--- a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php
+++ b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php
@@ -179,6 +179,36 @@ public function testBuildByExtensionWithMissingInformation() {
   }
 
   /**
+   * Tests the version property, and how it propagates to the contained assets.
+   *
+   * @covers ::buildByExtension
+   */
+  public function testVersion() {
+    $this->moduleHandler->expects($this->atLeastOnce())
+      ->method('moduleExists')
+      ->with('versions')
+      ->will($this->returnValue(TRUE));
+
+    $path = __DIR__ . '/library_test_files';
+    $path = substr($path, strlen($this->root) + 1);
+    $this->libraryDiscoveryParser->setPaths('module', 'versions', $path);
+
+    $libraries = $this->libraryDiscoveryParser->buildByExtension('versions');
+
+    $this->assertFalse(array_key_exists('version', $libraries['versionless']));
+    $this->assertEquals(-1, $libraries['versionless']['css'][0]['version']);
+    $this->assertEquals(-1, $libraries['versionless']['js'][0]['version']);
+
+    $this->assertEquals('9.8.7.6', $libraries['versioned']['version']);
+    $this->assertEquals('9.8.7.6', $libraries['versioned']['css'][0]['version']);
+    $this->assertEquals('9.8.7.6', $libraries['versioned']['js'][0]['version']);
+
+    $this->assertEquals(\Drupal::VERSION, $libraries['core-versioned']['version']);
+    $this->assertEquals(\Drupal::VERSION, $libraries['core-versioned']['css'][0]['version']);
+    $this->assertEquals(\Drupal::VERSION, $libraries['core-versioned']['js'][0]['version']);
+  }
+
+  /**
    * Tests that the version property of external libraries is handled.
    *
    * @covers ::buildByExtension
@@ -193,10 +223,11 @@ public function testExternalLibraries() {
     $path = substr($path, strlen($this->root) + 1);
     $this->libraryDiscoveryParser->setPaths('module', 'external', $path);
 
-    $libraries = $this->libraryDiscoveryParser->buildByExtension('external', 'example_external');
+    $libraries = $this->libraryDiscoveryParser->buildByExtension('external');
     $library = $libraries['example_external'];
 
-    $this->assertEquals($path . '/css/example_external.css', $library['css'][0]['data']);
+    $this->assertEquals('http://example.com/css/example_external.css', $library['css'][0]['data']);
+    $this->assertEquals('http://example.com/example_external.js', $library['js'][0]['data']);
     $this->assertEquals('3.14', $library['version']);
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset.css
new file mode 100644
index 0000000..ba7696d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset.css
@@ -0,0 +1 @@
+@charset "UTF-8";html{font-family:"sans-serif";}
diff --git a/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset.css.optimized.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset.css.optimized.css
new file mode 100644
index 0000000..c9e6ade
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset.css.optimized.css
@@ -0,0 +1 @@
+html{font-family:"sans-serif";}
diff --git a/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset_newline.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset_newline.css
new file mode 100644
index 0000000..68f3f42
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset_newline.css
@@ -0,0 +1,2 @@
+@charset 'UTF-8';
+html{font-family:"sans-serif";}
diff --git a/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset_sameline.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset_sameline.css
new file mode 100644
index 0000000..ba7696d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset_sameline.css
@@ -0,0 +1 @@
+@charset "UTF-8";html{font-family:"sans-serif";}
diff --git a/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset_sameline.css.optimized.css b/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset_sameline.css.optimized.css
new file mode 100644
index 0000000..c9e6ade
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/css_test_files/charset_sameline.css.optimized.css
@@ -0,0 +1 @@
+html{font-family:"sans-serif";}
diff --git a/core/tests/Drupal/Tests/Core/Asset/library_test_files/external.libraries.yml b/core/tests/Drupal/Tests/Core/Asset/library_test_files/external.libraries.yml
index 0486a72..c4be4c9 100644
--- a/core/tests/Drupal/Tests/Core/Asset/library_test_files/external.libraries.yml
+++ b/core/tests/Drupal/Tests/Core/Asset/library_test_files/external.libraries.yml
@@ -1,5 +1,7 @@
 example_external:
   version: v3.14
+  js:
+    http://example.com/example_external.js: { type: external }
   css:
     theme:
-      css/example_external.css: {}
+      http://example.com/css/example_external.css: { type: external }
diff --git a/core/tests/Drupal/Tests/Core/Asset/library_test_files/versions.libraries.yml b/core/tests/Drupal/Tests/Core/Asset/library_test_files/versions.libraries.yml
new file mode 100644
index 0000000..cc3d106
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/library_test_files/versions.libraries.yml
@@ -0,0 +1,22 @@
+versionless:
+  css:
+    theme:
+      versionless.css: {}
+  js:
+    versionless.js: {}
+
+versioned:
+  version: 9.8.7.6
+  css:
+    theme:
+      versioned.css: {}
+  js:
+    versioned.js: {}
+
+core-versioned:
+  version: VERSION
+  css:
+    theme:
+      core-versioned.css: {}
+  js:
+    core-versioned.js: {}
