diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index cfbaf473de52ee6aefb5c51ee7b8ba06f385c611..179382bf0c93ae62d578647f8ac8983a7cc84ccb 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -52,6 +52,10 @@ drupal:
 
 drupalSettings:
   version: VERSION
+  js:
+    # Need to specify a negative weight like drupal.js until
+    # https://www.drupal.org/node/1945262 is resolved.
+    misc/drupalSettingsLoader.js: { weight: -18 }
   drupalSettings:
     # These placeholder values will be set by system_js_settings_alter().
     path:
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
index 01f75ac887558e7b4d45f12ea4653e3089c97567..70832e9d9d1027b5240043666fed1bde39e58ad0 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
@@ -51,12 +51,6 @@ public function render(array $js_assets) {
     // query-string instead, to enforce reload on every page request.
     $default_query_string = $this->state->get('system.css_js_query_string') ?: '0';
 
-    // For inline JavaScript to validate as XHTML, all JavaScript containing
-    // XHTML needs to be wrapped in CDATA. To make that backwards compatible
-    // with HTML 4, we need to comment out the CDATA-tag.
-    $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
-    $embed_suffix = "\n//--><!]]>\n";
-
     // Defaults for each SCRIPT element.
     $element_defaults = array(
       '#type' => 'html_tag',
@@ -73,9 +67,13 @@ public function render(array $js_assets) {
       // Element properties that depend on item type.
       switch ($js_asset['type']) {
         case 'setting':
-          $element['#value_prefix'] = $embed_prefix;
-          $element['#value'] = 'var drupalSettings = ' . Json::encode($js_asset['data']) . ";";
-          $element['#value_suffix'] = $embed_suffix;
+          $element['#attributes'] = array(
+            // This type attribute prevents this from being parsed as an
+            // inline script.
+            'type' => 'application/json',
+            'data-drupal-selector' => 'drupal-settings-json',
+          );
+          $element['#value'] =  Json::encode($js_asset['data']);
           break;
 
         case 'file':
diff --git a/core/misc/drupal.js b/core/misc/drupal.js
index 75f85bbacdef78ec093c713a3aa96c32c13caf30..8ef8167b73679891703f20989c55da7fca1f8266 100644
--- a/core/misc/drupal.js
+++ b/core/misc/drupal.js
@@ -11,14 +11,6 @@
  * @prop {number} length=0
  */
 
-/**
- * Variable generated by Drupal with all the configuration created from PHP.
- *
- * @global
- *
- * @var {object} drupalSettings
- */
-
 /**
  * Variable generated by Drupal that holds all translated strings from PHP.
  *
diff --git a/core/misc/drupalSettingsLoader.js b/core/misc/drupalSettingsLoader.js
new file mode 100644
index 0000000000000000000000000000000000000000..ab3911dea88f8afa10303716a2e047c7442db1a7
--- /dev/null
+++ b/core/misc/drupalSettingsLoader.js
@@ -0,0 +1,24 @@
+/**
+ * @file
+ * Parse inline JSON and initialize the drupalSettings global object.
+ */
+
+(function () {
+
+  "use strict";
+
+  var settingsElement = document.querySelector('script[type="application/json"][data-drupal-selector="drupal-settings-json"]');
+
+  /**
+   * Variable generated by Drupal with all the configuration created from PHP.
+   *
+   * @global
+   *
+   * @type {object}
+   */
+  window.drupalSettings = {};
+
+  if (settingsElement !== null) {
+    window.drupalSettings = JSON.parse(settingsElement.textContent);
+  }
+})();
diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
index 1918d424545388464b57cb94eda9889e4abb3fb8..a425f4f954c66bcfc7b3433e68bd6401256a10c4 100644
--- a/core/modules/simpletest/src/AssertContentTrait.php
+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -66,7 +66,7 @@ protected function setRawContent($content) {
     $this->plainTextContent = NULL;
     $this->elements = NULL;
     $this->drupalSettings = array();
-    if (preg_match('/var drupalSettings = (.*?);$/m', $content, $matches)) {
+    if (preg_match('@<script type="application/json" data-drupal-selector="drupal-settings-json">([^<]*)</script>@', $content, $matches)) {
       $this->drupalSettings = Json::decode($matches[1]);
     }
   }
diff --git a/core/modules/system/src/Tests/Common/AttachedAssetsTest.php b/core/modules/system/src/Tests/Common/AttachedAssetsTest.php
index 52e3933982d56bb6fed1bdaa25ea55a2ed738d31..43f2ef9ab9bb8e0be0baebac63fb3b6f9e8f06c3 100644
--- a/core/modules/system/src/Tests/Common/AttachedAssetsTest.php
+++ b/core/modules/system/src/Tests/Common/AttachedAssetsTest.php
@@ -185,9 +185,9 @@ function testAggregation() {
     $rendered_footer_js = \Drupal::service('asset.js.collection_renderer')->render($footer_js);
     $this->assertTrue(
       count($rendered_footer_js) == 2
-      && substr($rendered_footer_js[0]['#value'], 0, 20) === 'var drupalSettings ='
+      && $rendered_footer_js[0]['#attributes']['data-drupal-selector'] === 'drupal-settings-json'
       && substr($rendered_footer_js[1]['#attributes']['src'], 0, 7) === 'http://',
-      'There are 2 JavaScript assets in the footer: one with drupalSettings, one with the sole aggregated JavaScript asset.'
+      'There are 2 JavaScript assets in the footer: one with drupal settings, one with the sole aggregated JavaScript asset.'
     );
   }
 
@@ -206,9 +206,9 @@ function testSettings() {
     $rendered_js = $this->renderer->renderPlain($js_render_array);
 
     // Parse the generated drupalSettings <script> back to a PHP representation.
-    $startToken = 'drupalSettings = ';
+    $startToken = '{';
     $endToken = '}';
-    $start = strpos($rendered_js, $startToken) + strlen($startToken);
+    $start = strpos($rendered_js, $startToken);
     $end = strrpos($rendered_js, $endToken);
     $json  = Unicode::substr($rendered_js, $start, $end - $start + 1);
     $parsed_settings = Json::decode($json);