diff --git a/core/includes/common.inc b/core/includes/common.inc
index 1581de6..0bcc469 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1685,26 +1685,6 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
         $setting['ajaxPageState']['theme_token'] = \Drupal::csrfToken()->get($theme_key);
       }
 
-      // Provide the page with information about the individual JavaScript files
-      // used, information not otherwise available when aggregation is enabled.
-      $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($javascript), 1);
-      unset($setting['ajaxPageState']['js']['settings']);
-
-      // Provide the page with information about the individual CSS files used,
-      // information not otherwise available when CSS aggregation is enabled.
-      // The setting is attached later in this function, but is set here, so
-      // that CSS files removed in drupal_process_attached() are still
-      // considered "used" and prevented from being added in a later AJAX
-      // request.
-      // Skip if no files were added to the page otherwise jQuery.extend() will
-      // overwrite the drupalSettings.ajaxPageState.css object with an empty
-      // array.
-      $css = _drupal_add_css();
-      if (!empty($css)) {
-        // Cast the array to an object to be on the safe side even if not empty.
-        $setting['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1);
-      }
-
       _drupal_add_js($setting, 'setting');
 
       // If we're outputting the header scope, then this might be the final time
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php
index ab07555..db6ac69 100644
--- a/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php
+++ b/core/lib/Drupal/Core/Asset/CssCollectionRenderer.php
@@ -94,6 +94,7 @@ public function render(array $css_assets) {
             $element['#attributes']['href'] = file_create_url($css_asset['data']) . $query_string_separator . $query_string;
             $element['#attributes']['media'] = $css_asset['media'];
             $element['#browsers'] = $css_asset['browsers'];
+            $element['#attributes']['data-css-files'] = $css_asset['data'];
             $elements[] = $element;
           }
           // The current page will run into IE's limits for CSS assets: work
@@ -107,12 +108,14 @@ public function render(array $css_assets) {
               $element['#attributes']['href'] = file_create_url($css_asset['data']) . $query_string_separator . $query_string;
               $element['#attributes']['media'] = $css_asset['media'];
               $element['#browsers'] = $css_asset['browsers'];
+              $element['#attributes']['data-css-files'] = $css_asset['data'];
               $elements[] = $element;
             }
             // The file CSS asset can be aggregated, but hasn't been: combine
             // multiple items into as few STYLE tags as possible.
             else {
               $import = array();
+              $files = array();
               // Start with the current CSS asset, iterate over subsequent CSS
               // assets and find which ones have the same 'type', 'group',
               // 'every_page', 'preprocess', 'media' and 'browsers' properties.
@@ -125,6 +128,7 @@ public function render(array $css_assets) {
                 // the @import statement, so we instead specify the media for
                 // the group on the STYLE tag.
                 $import[] = '@import url("' . String::checkPlain(file_create_url($next_css_asset['data']) . '?' . $query_string) . '");';
+                $files[] = $next_css_asset['data'];
                 // Move the outer for loop skip the next item, since we
                 // processed it here.
                 $i = $j;
@@ -152,6 +156,7 @@ public function render(array $css_assets) {
                 $element['#value'] = "\n" . implode("\n", $import_batch) . "\n";
                 $element['#attributes']['media'] = $css_asset['media'];
                 $element['#browsers'] = $css_asset['browsers'];
+                $element['#attributes']['data-css-files'] = implode(',', $files);
                 $elements[] = $element;
               }
             }
@@ -180,6 +185,7 @@ public function render(array $css_assets) {
           $element['#attributes']['href'] = $css_asset['data'];
           $element['#attributes']['media'] = $css_asset['media'];
           $element['#browsers'] = $css_asset['browsers'];
+          $element['#attributes']['data-css-files'] = $css_asset['data'];
           $elements[] = $element;
           break;
 
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
index 807179f..c7f8a08 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
@@ -87,10 +87,12 @@ public function render(array $js_assets) {
           if (!isset($js_asset['preprocessed'])) {
             $element['#attributes']['src'] .= $query_string_separator . ($js_asset['cache'] ? $query_string : REQUEST_TIME);
           }
+          $element['#attributes']['data-js-files'] = $js_asset['data'];
           break;
 
         case 'external':
           $element['#attributes']['src'] = $js_asset['data'];
+          $element['#attributes']['data-js-files'] = $js_asset['data'];
           break;
 
         default:
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 705d592..b89a7e2 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -361,6 +361,17 @@
    * before field data is collected.
    */
   Drupal.ajax.prototype.beforeSerialize = function (element, options) {
+    // Helper function to split a list of filenames and add them to the proper
+    // ajax_page_state value.
+    function addFiles (type, files) {
+      if (files) {
+        files = files.split(',');
+        for (var i = 0, il = files.length; i < il; i += 1) {
+          options.data['ajax_page_state[' + type + '][' + files[i] + ']'] = 1;
+        }
+      }
+    }
+
     // Allow detaching behaviors to update field values before collecting them.
     // This is only needed when field values are added to the POST data, so only
     // when there is a form such that this.$form.ajaxSubmit() is used instead of
@@ -389,16 +400,16 @@
     var pageState = drupalSettings.ajaxPageState;
     options.data['ajax_page_state[theme]'] = pageState.theme;
     options.data['ajax_page_state[theme_token]'] = pageState.theme_token;
-    for (var cssFile in pageState.css) {
-      if (pageState.css.hasOwnProperty(cssFile)) {
-        options.data['ajax_page_state[css][' + cssFile + ']'] = 1;
-      }
-    }
     for (var jsFile in pageState.js) {
       if (pageState.js.hasOwnProperty(jsFile)) {
         options.data['ajax_page_state[js][' + jsFile + ']'] = 1;
       }
     }
+    var files_regex = /data-(css|js)-files="([^"]+)"/g;
+    var match;
+    while ((match = files_regex.exec(document.documentElement.innerHTML)) !== null) {
+      addFiles(match[1], match[2]);
+    }
   };
 
   /**
diff --git a/core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php b/core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php
index 72bac19..5ffeb5f 100644
--- a/core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php
+++ b/core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php
@@ -107,8 +107,10 @@ function testLoading() {
     $this->assertTrue(count($format_selector) === 1, 'A single text format selector exists on the page.');
     $specific_format_selector = $this->xpath('//select[contains(@class, "filter-list") and contains(@class, "editor") and @data-editor-for="edit-body-0-value"]');
     $this->assertTrue(count($specific_format_selector) === 1, 'A single text format selector exists on the page and has the "editor" class and a "data-editor-for" attribute with the correct value.');
-    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/ckeditor/js/ckeditor.js']), 'CKEditor glue JS is present.');
-    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/assets/vendor/ckeditor/ckeditor.js']), 'CKEditor lib JS is present.');
+    $glue_js_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/modules/ckeditor/js/ckeditor.js', array('absolute' => TRUE)) . '")]');
+    $this->assertTrue($glue_js_present, 'CKEditor glue JS is present.');
+    $ckeditor_js_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/assets/vendor/ckeditor/ckeditor.js', array('absolute' => TRUE)) . '")]');
+    $this->assertTrue($ckeditor_js_present, 'CKEditor lib JS is present.');
 
     // Enable the ckeditor_test module, customize configuration. In this case,
     // there is additional CSS and JS to be loaded.
@@ -136,8 +138,10 @@ function testLoading() {
     $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
     $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
     $this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
-    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/ckeditor/js/ckeditor.js']), 'CKEditor glue JS is present.');
-    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/assets/vendor/ckeditor/ckeditor.js']), 'CKEditor lib JS is present.');
+    $glue_js_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/modules/ckeditor/js/ckeditor.js', array('absolute' => TRUE)) . '")]');
+    $this->assertTrue($glue_js_present, 'CKEditor glue JS is present.');
+    $ckeditor_js_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/assets/vendor/ckeditor/ckeditor.js', array('absolute' => TRUE)) . '")]');
+    $this->assertTrue($ckeditor_js_present, 'CKEditor lib JS is present.');
   }
 
   protected function getThingsToCheck() {
@@ -148,7 +152,7 @@ protected function getThingsToCheck() {
       // Editor.module's JS settings present.
       isset($settings['editor']),
       // Editor.module's JS present.
-      isset($settings['ajaxPageState']['js']['core/modules/editor/js/editor.js']),
+      $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/modules/editor/js/editor.js', array('absolute' => TRUE)) . '")]'),
       // Body field.
       $this->xpath('//textarea[@id="edit-body-0-value"]'),
       // Format selector.
diff --git a/core/modules/comment/src/Tests/CommentCSSTest.php b/core/modules/comment/src/Tests/CommentCSSTest.php
index a3d8a61..8de1a21 100644
--- a/core/modules/comment/src/Tests/CommentCSSTest.php
+++ b/core/modules/comment/src/Tests/CommentCSSTest.php
@@ -110,7 +110,8 @@ function testCommentClasses() {
         // user (the viewer) was the author of the comment. We do this in Java-
         // Script to prevent breaking the render cache.
         $this->assertIdentical(1, count($this->xpath('//*[contains(@class, "comment") and @data-comment-user-id="' . $case['comment_uid'] . '"]')), 'data-comment-user-id attribute is set on comment.');
-        $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/comment/js/comment-by-viewer.js']), 'drupal.comment-by-viewer library is present.');
+        $comment_by_viewer_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/modules/comment/js/comment-by-viewer.js', array('absolute' => TRUE)) . '")]');
+        $this->assertTrue($comment_by_viewer_present, 'drupal.comment-by-viewer library is present.');
       }
 
       // Verify the unpublished class.
@@ -129,7 +130,8 @@ function testCommentClasses() {
       if ($case['comment_status'] == CommentInterface::PUBLISHED || $case['user'] == 'admin') {
         $this->assertIdentical(1, count($this->xpath('//*[contains(@class, "comment")]/*[@data-comment-timestamp="' . $comment->getChangedTime() . '"]')), 'data-comment-timestamp attribute is set on comment');
         $expectedJS = ($case['user'] !== 'anonymous');
-        $this->assertIdentical($expectedJS, isset($settings['ajaxPageState']['js']['core/modules/comment/js/comment-new-indicator.js']), 'drupal.comment-new-indicator library is present.');
+        $comment_new_indicator_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/modules/comment/js/comment-new-indicator.js', array('absolute' => TRUE)) . '")]');
+        $this->assertEqual($expectedJS, $comment_new_indicator_present, 'drupal.comment-new-indicator library is present.');
       }
     }
   }
diff --git a/core/modules/editor/src/Tests/EditorLoadingTest.php b/core/modules/editor/src/Tests/EditorLoadingTest.php
index 387e6e6..baac47b 100644
--- a/core/modules/editor/src/Tests/EditorLoadingTest.php
+++ b/core/modules/editor/src/Tests/EditorLoadingTest.php
@@ -161,7 +161,7 @@ protected function getThingsToCheck() {
       // Editor.module's JS settings present.
       isset($settings['editor']),
       // Editor.module's JS present.
-      isset($settings['ajaxPageState']['js']['core/modules/editor/js/editor.js']),
+      $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/modules/editor/js/editor.js', array('absolute' => TRUE)) . '")]'),
       // Body field.
       $this->xpath('//textarea[@id="edit-body-0-value"]'),
       // Format selector.
diff --git a/core/modules/history/src/Tests/HistoryTest.php b/core/modules/history/src/Tests/HistoryTest.php
index f0f05b8..a71bdf4 100644
--- a/core/modules/history/src/Tests/HistoryTest.php
+++ b/core/modules/history/src/Tests/HistoryTest.php
@@ -119,7 +119,8 @@ function testHistory() {
     $this->drupalGet('node/' . $nid);
     // JavaScript present to record the node read.
     $settings = $this->drupalGetSettings();
-    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/history/js/history.js']), 'drupal.history library is present.');
+    $history_js_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/modules/history/js/history.js', array('absolute' => TRUE)) . '")]');
+    $this->assertTrue($history_js_present, 'drupal.history library is present.');
     $this->assertRaw('Drupal.history.markAsRead(' . $nid . ')', 'History module JavaScript API call to mark node as read present on page.');
 
     // Simulate JavaScript: perform HTTP request to mark node as read.
diff --git a/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php b/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php
index a20663d..7f1948b 100644
--- a/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php
+++ b/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php
@@ -126,8 +126,10 @@ public function testUserWithPermission() {
 
     // Library and in-place editors.
     $settings = $this->drupalGetSettings();
-    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/quickedit/js/quickedit.js']), 'Quick Edit library loaded.');
-    $this->assertFalse(isset($settings['ajaxPageState']['js']['core/modules/quickedit/js/editors/formEditor.js']), "'form' in-place editor not loaded.");
+    $quickedit_js_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/modules/quickedit/js/quickedit.js', array('absolute' => TRUE)) . '")]');
+    $this->assertTrue($quickedit_js_present, 'Quick Edit library loaded.');
+    $form_editor_js_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/modules/quickedit/js/editors/formEditor.js', array('absolute' => TRUE)) . '")]');
+    $this->assertFalse($form_editor_js_present, "'form' in-place editor not loaded.");
 
     // HTML annotation must always exist (to not break the render cache).
     $this->assertRaw('data-quickedit-entity-id="node/1"');
@@ -168,7 +170,8 @@ public function testUserWithPermission() {
     $this->assertIdentical('settings', $ajax_commands[0]['command'], 'The first AJAX command is a settings command.');
     // Second command: insert libraries into DOM.
     $this->assertIdentical('insert', $ajax_commands[1]['command'], 'The second AJAX command is an append command.');
-    $this->assertTrue(in_array('core/modules/quickedit/js/editors/formEditor.js', array_keys($ajax_commands[0]['settings']['ajaxPageState']['js'])), 'The quickedit.inPlaceEditor.form library is loaded.');
+    $form_editor_js_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/modules/quickedit/js/editors/formEditor.js', array('absolute' => TRUE)) . '")]');
+    $this->assertTrue($form_editor_js_present, 'The quickedit.inPlaceEditor.form library is loaded.');
 
     // Retrieving the form for this field should result in a 200 response,
     // containing only a quickeditFieldForm command.
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 3c826bf..0b41f1e 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -1951,12 +1951,6 @@ protected function getAjaxPageStatePostData() {
     if (isset($drupal_settings['ajaxPageState'])) {
       $post['ajax_page_state[theme]'] = $drupal_settings['ajaxPageState']['theme'];
       $post['ajax_page_state[theme_token]'] = $drupal_settings['ajaxPageState']['theme_token'];
-      foreach ($drupal_settings['ajaxPageState']['css'] as $key => $value) {
-        $post["ajax_page_state[css][$key]"] = 1;
-      }
-      foreach ($drupal_settings['ajaxPageState']['js'] as $key => $value) {
-        $post["ajax_page_state[js][$key]"] = 1;
-      }
     }
     return $post;
   }
diff --git a/core/modules/system/src/Tests/Ajax/DialogTest.php b/core/modules/system/src/Tests/Ajax/DialogTest.php
index 867ed11..a6b1d4e 100644
--- a/core/modules/system/src/Tests/Ajax/DialogTest.php
+++ b/core/modules/system/src/Tests/Ajax/DialogTest.php
@@ -134,9 +134,12 @@ public function testDialog() {
     $ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', array(), 'button1');
 
     // Check that CSS and JavaScript are "added" to the page dynamically.
-    $this->assertTrue(in_array('jquery.ui.dialog.css', array_keys($ajax_result[0]['settings']['ajaxPageState']['css'])), 'jQuery UI dialog CSS added to the page.');
-    $this->assertTrue(in_array('core/assets/vendor/jquery.ui/ui/jquery.ui.dialog.js', array_keys($ajax_result[0]['settings']['ajaxPageState']['js'])), 'jQuery UI dialog JS added to the page.');
-    $this->assertTrue(in_array('core/misc/dialog/dialog.ajax.js', array_keys($ajax_result[0]['settings']['ajaxPageState']['js'])), 'Drupal dialog JS added to the page.');
+    $ui_dialog_css_present = $this->xpath('//link[contains(@href, "' . $this->container->get('url_generator')->generateFromPath('jquery.ui.dialog.css', array('absolute' => TRUE)) . '")]');
+    $this->assertTrue($ui_dialog_css_present, 'jQuery UI dialog CSS added to the page.');
+    $ui_dialog_js_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/assets/vendor/jquery.ui/ui/jquery.ui.dialog.js', array('absolute' => TRUE)) . '")]');
+    $this->assertTrue($ui_dialog_js_present, 'jQuery UI dialog JS added to the page.');
+    $dialog_ajax_js_present = $this->xpath('//script[contains(@src, "' . $this->container->get('url_generator')->generateFromPath('core/misc/dialog/dialog.ajax.js', array('absolute' => TRUE)) . '")]');
+    $this->assertTrue($dialog_ajax_js_present, 'Drupal dialog JS added to the page.');
 
     // Check that the response matches the expected value.
     $this->assertEqual($modal_expected_response, $ajax_result[3], 'POST request modal dialog JSON response matches.');
diff --git a/core/modules/system/src/Tests/Ajax/FrameworkTest.php b/core/modules/system/src/Tests/Ajax/FrameworkTest.php
index 0928c2a..1342970 100644
--- a/core/modules/system/src/Tests/Ajax/FrameworkTest.php
+++ b/core/modules/system/src/Tests/Ajax/FrameworkTest.php
@@ -111,96 +111,6 @@ public function testAJAXRenderError() {
   }
 
   /**
-   * Tests that new JavaScript and CSS files are lazy-loaded on an AJAX request.
-   */
-  public function testLazyLoad() {
-    $expected = array(
-      'setting_name' => 'ajax_forms_test_lazy_load_form_submit',
-      'setting_value' => 'executed',
-      'css' => drupal_get_path('module', 'system') . '/css/system.admin.css',
-      'js' => drupal_get_path('module', 'system') . '/system.js',
-    );
-    // CSS files are stored by basename, see _drupal_add_css().
-    $expected_css_basename = drupal_basename($expected['css']);
-
-    // @todo D8: Add a drupal_css_defaults() helper function.
-    $expected_css_html = drupal_get_css(array($expected_css_basename => array(
-      'type' => 'file',
-      'group' => CSS_AGGREGATE_DEFAULT,
-      'weight' => 0,
-      'every_page' => FALSE,
-      'media' => 'all',
-      'preprocess' => TRUE,
-      'data' => $expected['css'],
-      'browsers' => array('IE' => TRUE, '!IE' => TRUE),
-    )), TRUE);
-    $expected_js_html = drupal_get_js('header', array($expected['js'] => drupal_js_defaults($expected['js'])), TRUE);
-
-    // Get the base page.
-    $this->drupalGet('ajax_forms_test_lazy_load_form');
-    $original_settings = $this->drupalGetSettings();
-    $original_css = $original_settings['ajaxPageState']['css'];
-    $original_js = $original_settings['ajaxPageState']['js'];
-
-    // Verify that the base page doesn't have the settings and files that are to
-    // be lazy loaded as part of the next requests.
-    $this->assertTrue(!isset($original_settings[$expected['setting_name']]), format_string('Page originally lacks the %setting, as expected.', array('%setting' => $expected['setting_name'])));
-    $this->assertTrue(!isset($original_css[$expected['css']]), format_string('Page originally lacks the %css file, as expected.', array('%css' => $expected['css'])));
-    $this->assertTrue(!isset($original_js[$expected['js']]), format_string('Page originally lacks the %js file, as expected.', array('%js' => $expected['js'])));
-
-    // Submit the AJAX request without triggering files getting added.
-    $commands = $this->drupalPostAjaxForm(NULL, array('add_files' => FALSE), array('op' => t('Submit')));
-    $new_settings = $this->drupalGetSettings();
-    $new_css = $new_settings['ajaxPageState']['css'];
-    $new_js = $new_settings['ajaxPageState']['js'];
-
-    // Verify the setting was not added when not expected.
-    $this->assertTrue(!isset($new_settings[$expected['setting_name']]), format_string('Page still lacks the %setting, as expected.', array('%setting' => $expected['setting_name'])));
-    $this->assertTrue(!isset($new_css[$expected['css']]), format_string('Page still lacks the %css file, as expected.', array('%css' => $expected['css'])));
-    $this->assertTrue(!isset($new_js[$expected['js']]), format_string('Page still lacks the %js file, as expected.', array('%js' => $expected['js'])));
-    // Verify a settings command does not add CSS or scripts to drupalSettings
-    // and no command inserts the corresponding tags on the page.
-    $found_settings_command = FALSE;
-    $found_markup_command = FALSE;
-    foreach ($commands as $command) {
-      if ($command['command'] == 'settings' && (array_key_exists('css', $command['settings']['ajaxPageState']) || array_key_exists('js', $command['settings']['ajaxPageState']))) {
-        $found_settings_command = TRUE;
-      }
-      if (isset($command['data']) && ($command['data'] == $expected_js_html || $command['data'] == $expected_css_html)) {
-        $found_markup_command = TRUE;
-      }
-    }
-    $this->assertFalse($found_settings_command, format_string('Page state still lacks the %css and %js files, as expected.', array('%css' => $expected['css'], '%js' => $expected['js'])));
-    $this->assertFalse($found_markup_command, format_string('Page still lacks the %css and %js files, as expected.', array('%css' => $expected['css'], '%js' => $expected['js'])));
-
-    // Submit the AJAX request and trigger adding files.
-    $commands = $this->drupalPostAjaxForm(NULL, array('add_files' => TRUE), array('op' => t('Submit')));
-    $new_settings = $this->drupalGetSettings();
-    $new_css = $new_settings['ajaxPageState']['css'];
-    $new_js = $new_settings['ajaxPageState']['js'];
-
-    // Verify the expected setting was added, both to drupalSettings, and as
-    // the first AJAX command.
-    $this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], format_string('Page now has the %setting.', array('%setting' => $expected['setting_name'])));
-    $expected_command = new SettingsCommand(array($expected['setting_name'] => $expected['setting_value']), TRUE);
-    $this->assertCommand(array_slice($commands, 0, 1), $expected_command->render(), format_string('The settings command was first.'));
-
-    // Verify the expected CSS file was added, both to drupalSettings, and as
-    // the second AJAX command for inclusion into the HTML.
-    $this->assertEqual($new_css, $original_css + array($expected_css_basename => 1), format_string('Page state now has the %css file.', array('%css' => $expected['css'])));
-    $this->assertCommand(array_slice($commands, 1, 1), array('data' => $expected_css_html), format_string('Page now has the %css file.', array('%css' => $expected['css'])));
-
-    // Verify the expected JS file was added, both to drupalSettings, and as
-    // the third AJAX command for inclusion into the HTML. By testing for an
-    // exact HTML string containing the SCRIPT tag, we also ensure that
-    // unexpected JavaScript code, such as a jQuery.extend() that would
-    // potentially clobber rather than properly merge settings, didn't
-    // accidentally get added.
-    $this->assertEqual($new_js, $original_js + array($expected['js'] => 1), format_string('Page state now has the %js file.', array('%js' => $expected['js'])));
-    $this->assertCommand(array_slice($commands, 2, 1), array('data' => $expected_js_html), format_string('Page now has the %js file.', array('%js' => $expected['js'])));
-  }
-
-  /**
    * Tests that drupalSettings.currentPath is not updated on AJAX requests.
    */
   public function testCurrentPathChange() {
diff --git a/core/modules/system/src/Tests/Common/JavaScriptTest.php b/core/modules/system/src/Tests/Common/JavaScriptTest.php
index 8680765..23907e8 100644
--- a/core/modules/system/src/Tests/Common/JavaScriptTest.php
+++ b/core/modules/system/src/Tests/Common/JavaScriptTest.php
@@ -112,11 +112,11 @@ function testAttributes() {
     $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>';
+    $regex_1 = '/<script([^src].*)src=' . preg_quote('"http://example.com/script.js"', '/') . '([^defer].*)defer="defer">/';
+    $regex_2 = '/<script([^src].*)src=' . preg_quote('"' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '"', '/') . '([^defer].*)defer="defer">/';
 
-    $this->assertTrue(strpos($javascript, $expected_1) > 0, 'Rendered external JavaScript with correct defer attribute.');
-    $this->assertTrue(strpos($javascript, $expected_2) > 0, 'Rendered internal JavaScript with correct defer attribute.');
+    $this->assertTrue(preg_match($regex_1, $javascript), 'Rendered external JavaScript with correct defer attribute.');
+    $this->assertTrue(preg_match($regex_2, $javascript), 'Rendered internal JavaScript with correct defer attribute.');
   }
 
   /**
@@ -139,11 +139,11 @@ function testAggregatedAttributes() {
     $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>';
+    $regex_1 = '/<script([^src].*)src=' . preg_quote('"http://example.com/script.js"', '/') . '([^defer].*)defer="defer">/';
+    $regex_2 = '/<script([^src].*)src=' . preg_quote('"' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '"', '/') . '([^defer].*)defer="defer">/';
 
-    $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.');
+    $this->assertTrue(preg_match($regex_1, $javascript), 'Rendered external JavaScript with correct defer attribute with aggregation enabled.');
+    $this->assertTrue(preg_match($regex_2, $javascript), 'Rendered internal JavaScript with correct defer attribute with aggregation enabled.');
   }
 
   /**
@@ -358,7 +358,7 @@ function testBrowserConditionalComments() {
     $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_1 = "<!--[if lte IE 8]>\n" . '<script src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '"';
     $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.');
@@ -396,10 +396,10 @@ function testAggregation() {
     $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>',
+      '<script src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '" data-js-files="core/misc/collapse.js"></script>',
+      '<script src="' . file_create_url('core/misc/batch.js') . '?' . $default_query_string . '" data-js-files="core/misc/batch.js"></script>',
+      '<script src="' . file_create_url('core/misc/ajax.js') . '?' . $default_query_string . '" data-js-files="core/misc/ajax.js"></script>',
+      '<script src="' . file_create_url('core/misc/autocomplete.js') . '?' . $default_query_string . '" data-js-files="core/misc/autocomplete.js"></script>',
     ));
     $this->assertTrue(strpos($javascript, $expected) > 0, 'Unaggregated JavaScript is added in the expected group order.');
 
@@ -418,9 +418,13 @@ function testAggregation() {
     $this->render($attached);
     $js_items = _drupal_add_js();
     $javascript = drupal_get_js();
+    $collapse_batch_url = $this->calculateAggregateFilename(array('core/misc/collapse.js' => $js_items['core/misc/collapse.js'], 'core/misc/batch.js' => $js_items['core/misc/batch.js']));
+    $collapse_batch_path = $this->calculateAggregateFilename(array('core/misc/collapse.js' => $js_items['core/misc/collapse.js'], 'core/misc/batch.js' => $js_items['core/misc/batch.js']), FALSE);
+    $ajax_autocomplete_url = $this->calculateAggregateFilename(array('core/misc/ajax.js' => $js_items['core/misc/ajax.js'], 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js']));
+    $ajax_autocomplete_path = $this->calculateAggregateFilename(array('core/misc/ajax.js' => $js_items['core/misc/ajax.js'], 'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js']), FALSE);
     $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>',
+      '<script src="' . $collapse_batch_url . '" data-js-files="' . $collapse_batch_path . '"></script>',
+      '<script src="' . $ajax_autocomplete_url . '" data-js-files="' . $ajax_autocomplete_path . '"></script>',
     ));
     $this->assertTrue(strpos($javascript, $expected) !== FALSE, 'JavaScript is aggregated in the expected groups and order.');
   }
@@ -537,6 +541,7 @@ function testRenderOrder() {
       "-8 #3",
       "-8 #4",
       "-5 #1", // The external script.
+      "-5 #1", // The external script has the data-js-files attribute.
       "0 #1",
       "0 #2",
       "0 #3",
@@ -701,18 +706,20 @@ function testAddJsFileWithQueryString() {
    *
    * @param array $js_assets
    *   A group of JavaScript assets.
+   * @param bool $create_url
+   *   Creates a URL if TRUE, a path if FALSE.
    * @return string
    *   A file URI.
    *
    * @see testAggregation()
    * @see testAggregationOrder()
    */
-  protected function calculateAggregateFilename($js_assets) {
+  protected function calculateAggregateFilename($js_assets, $create_url = TRUE) {
     $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');
+    return $create_url ? file_create_url('public://js/js_' . Crypt::hashBase64($data) . '.js') : 'public://js/js_' . Crypt::hashBase64($data) . '.js';
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php
index 5028f45..ff3e154 100644
--- a/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionRendererUnitTest.php
@@ -154,6 +154,7 @@ function providerTestRender() {
           'rel' => 'stylesheet',
           'href' => $href,
           'media' => $media,
+          'data-css-files' => $href,
         ),
         '#browsers' => $browsers,
       );
