diff --git a/core/authorize.php b/core/authorize.php
index 35277f6b379822c21e9ddaa767a2b21607c5d7a4..d9f1e56a8d851c33d3fbf5f9c3376345d9f58a71 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -85,8 +85,10 @@ function authorize_access_allowed(Request $request) {
 $content = [];
 $show_messages = TRUE;
 
-$response = new Response();
-if (authorize_access_allowed($request)) {
+$is_allowed = authorize_access_allowed($request);
+
+// Build content.
+if ($is_allowed) {
   // Load both the Form API and Batch API.
   require_once __DIR__ . '/includes/form.inc';
   require_once __DIR__ . '/includes/batch.inc';
@@ -152,16 +154,16 @@ function authorize_access_allowed(Request $request) {
   $show_messages = !(($batch = batch_get()) && isset($batch['running']));
 }
 else {
-  $response->setStatusCode(403);
   \Drupal::logger('access denied')->warning('authorize.php');
   $page_title = t('Access denied');
   $content = ['#markup' => t('You are not allowed to access this page.')];
 }
 
-if (!empty($content)) {
-  $response->headers->set('Content-Type', 'text/html; charset=utf-8');
-  $response->setContent(\Drupal::service('bare_html_page_renderer')->renderBarePage($content, $page_title, 'maintenance_page', array(
-    '#show_messages' => $show_messages,
-  )));
-  $response->send();
+$bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
+$response = $bare_html_page_renderer->renderBarePage($content, $page_title, 'maintenance_page', array(
+  '#show_messages' => $show_messages,
+));
+if (!$is_allowed) {
+  $response->setStatusCode(403);
 }
+$response->send();
diff --git a/core/core.services.yml b/core/core.services.yml
index 6c2c78ff9e9045653806b55f14d900ab2ce386ce..d8a8d242291df05be9329a41f4df719035d4b644 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -814,8 +814,9 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@resolver_manager.entity']
-  ajax_subscriber:
-    class: Drupal\Core\EventSubscriber\AjaxSubscriber
+  ajax_response.subscriber:
+    class: Drupal\Core\EventSubscriber\AjaxResponseSubscriber
+    arguments: ['@ajax_response.attachments_processor']
     tags:
       - { name: event_subscriber }
   form_ajax_subscriber:
@@ -903,7 +904,7 @@ services:
     arguments: ['@router', '@router.request_context', NULL, '@request_stack']
   bare_html_page_renderer:
     class: Drupal\Core\Render\BareHtmlPageRenderer
-    arguments: ['@renderer']
+    arguments: ['@renderer', '@html_response.attachments_processor']
     lazy: true
   private_key:
     class: Drupal\Core\PrivateKey
@@ -975,6 +976,19 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@current_user']
+  ajax_response.attachments_processor:
+    class: Drupal\Core\Ajax\AjaxResponseAttachmentsProcessor
+    tags:
+    arguments: ['@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer', '@module_handler']
+  html_response.attachments_processor:
+    class: Drupal\Core\Render\HtmlResponseAttachmentsProcessor
+    tags:
+    arguments: ['@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer']
+  html_response.subscriber:
+    class: Drupal\Core\EventSubscriber\HtmlResponseSubscriber
+    tags:
+      - { name: event_subscriber }
+    arguments: ['@html_response.attachments_processor']
   finish_response_subscriber:
     class: Drupal\Core\EventSubscriber\FinishResponseSubscriber
     tags:
diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 9281837b6fbc77839e89d11bca55ff0df6bda05b..4e474715981ef187a9171cfef285ea0b9709ebbb 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -137,9 +137,14 @@ function _batch_progress_page() {
     // additional HTML output by PHP shows up inside the page rather than below
     // it. While this causes invalid HTML, the same would be true if we didn't,
     // as content is not allowed to appear after </html> anyway.
-    $fallback = \Drupal::service('bare_html_page_renderer')->renderBarePage(['#markup' => $fallback], $current_set['title'], 'maintenance_page', array(
+    $bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
+    $response = $bare_html_page_renderer->renderBarePage(['#markup' => $fallback], $current_set['title'], 'maintenance_page', array(
       '#show_messages' => FALSE,
     ));
+
+    // Just use the content of the response.
+    $fallback = $response->getContent();
+
     list($fallback) = explode('<!--partial-->', $fallback);
     print $fallback;
 
diff --git a/core/includes/errors.inc b/core/includes/errors.inc
index 6933dc8690c9e2777f7f645d106c3fc5631d5f0e..06753fd0dd650daadb0037b025177b4c1b558c46 100644
--- a/core/includes/errors.inc
+++ b/core/includes/errors.inc
@@ -9,7 +9,6 @@
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Logger\RfcLogLevel;
 use Drupal\Core\Utility\Error;
-use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Maps PHP error constants to watchdog severity levels.
@@ -241,12 +240,11 @@ function _drupal_log_error($error, $fatal = FALSE) {
           '#markup' => $message,
         );
         install_display_output($output, $GLOBALS['install_state']);
-      }
-      else {
-        $output = \Drupal::service('bare_html_page_renderer')->renderBarePage(['#markup' => $message], 'Error', 'maintenance_page');
+        exit;
       }
 
-      $response = new Response($output, 500);
+      $bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
+      $response = $bare_html_page_renderer->renderBarePage(['#markup' => $message], 'Error', 'maintenance_page');
       $response->setStatusCode(500, '500 Service unavailable (with message)');
       // An exception must halt script execution.
       $response->send();
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 402d41e9372f4dc9c011577c3ee718d2f82a7973..34bfcb0179fd2c9c5c680c3c588369302406b17a 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -984,7 +984,8 @@ function install_display_output($output, $install_state) {
     $regions['sidebar_first'] = $task_list;
   }
 
-  $response = new Response();
+  $bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
+  $response = $bare_html_page_renderer->renderBarePage($output, $output['#title'], 'install_page', $regions);
   $default_headers = array(
     'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT',
     'Last-Modified' => gmdate(DATE_RFC1123, REQUEST_TIME),
@@ -992,7 +993,6 @@ function install_display_output($output, $install_state) {
     'ETag' => '"' . REQUEST_TIME . '"',
   );
   $response->headers->add($default_headers);
-  $response->setContent(\Drupal::service('bare_html_page_renderer')->renderBarePage($output, $output['#title'], 'install_page', $regions));
   $response->send();
   exit;
 }
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 5d645e2350439f21e37ddf3cc798971aa445b9b5..74000172c5d242cde26dd4f220477aeb759edb2b 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -9,21 +9,17 @@
  */
 
 use Drupal\Component\Serialization\Json;
+use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\Unicode;
-use Drupal\Component\Utility\UrlHelper;
 use Drupal\Component\Utility\Xss;
-use Drupal\Core\Asset\AttachedAssets;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\StorageException;
-use Drupal\Core\Extension\Extension;
-use Drupal\Core\Extension\ExtensionNameLengthException;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Theme\ThemeSettings;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Render\Element;
-use Symfony\Component\HttpFoundation\Request;
 
 /**
  * @defgroup content_flags Content markers
@@ -1308,41 +1304,23 @@ function template_preprocess_html(&$variables) {
   // @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
   $variables['head_title_array'] = $head_title;
 
-  // Collect all attachments. This must happen in the preprocess function for
-  // #type => html, to ensure that attachments added in #pre_render callbacks
-  // for #type => html are included.
-  $attached = $variables['html']['#attached'];
-  $attached = drupal_merge_attached($attached, $variables['page']['#attached']);
-  if (isset($variables['page_top'])) {
-    $attached = drupal_merge_attached($attached, $variables['page_top']['#attached']);
+  // Create placeholder strings for these keys.
+  // @see \Drupal\Core\Render\HtmlResponseSubscriber
+  $types = [
+    'styles',
+    'scripts',
+    'scripts_bottom',
+    'head',
+  ];
+  $token = Crypt::randomBytesBase64(55);
+  foreach ($types as $type) {
+    $placeholder = SafeMarkup::format('<drupal-html-response-attachment-placeholder type="@type" token="@token"></drupal-html-response-attachment-placeholder>', [
+      '@type' => $type,
+      '@token' => $token,
+    ]);
+    $variables[$type]['#markup'] = $placeholder;
+    $variables[$type]['#attached']['html_response_placeholders'][$type] = $placeholder;
   }
-  if (isset($variables['page_bottom'])) {
-    $attached = drupal_merge_attached($attached, $variables['page_bottom']['#attached']);
-  }
-
-  // Render the attachments into HTML markup to be used directly in the template
-  // for #type => html: html.html.twig.
-  $all_attached = ['#attached' => $attached];
-  $assets = AttachedAssets::createFromRenderArray($all_attached);
-  // Take Ajax page state into account, to allow for something like Turbolinks
-  // to be implemented without altering core.
-  // @see https://github.com/rails/turbolinks/
-  $ajax_page_state = \Drupal::request()->request->get('ajax_page_state');
-  $assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []);
-  // Optimize CSS/JS if necessary, but only during normal site operation.
-  $optimize_css = !defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('css.preprocess');
-  $optimize_js = !defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('js.preprocess');
-  // Render the asset collections.
-  $asset_resolver = \Drupal::service('asset.resolver');
-  $variables['styles'] = \Drupal::service('asset.css.collection_renderer')->render($asset_resolver->getCssAssets($assets, $optimize_css));
-  list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, $optimize_js);
-  $js_collection_renderer = \Drupal::service('asset.js.collection_renderer');
-  $variables['scripts'] = $js_collection_renderer->render($js_assets_header);
-  $variables['scripts_bottom'] = $js_collection_renderer->render($js_assets_footer);
-
-  // Handle all non-asset attachments.
-  drupal_process_attached($all_attached);
-  $variables['head'] = drupal_get_html_head(FALSE);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
index 1acf03afc2d6c22f60e176dfd3964be8872f3178..a2218e664f0d91f7d35aa859d596370c76077815 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
@@ -10,6 +10,8 @@
 use Drupal\Core\Asset\AttachedAssets;
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Core\Render\Renderer;
+use Drupal\Core\Render\AttachmentsInterface;
+use Drupal\Core\Render\AttachmentsTrait;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
@@ -20,7 +22,9 @@
  *
  * @ingroup ajax
  */
-class AjaxResponse extends JsonResponse {
+class AjaxResponse extends JsonResponse implements AttachmentsInterface {
+
+  use AttachmentsTrait;
 
   /**
    * The array of ajax commands.
@@ -29,32 +33,6 @@ class AjaxResponse extends JsonResponse {
    */
   protected $commands = array();
 
-  /**
-   * The attachments for this Ajax response.
-   *
-   * @var array
-   */
-  protected $attachments = [
-    'library' => [],
-    'drupalSettings' => [],
-  ];
-
-  /**
-   * Sets attachments for this Ajax response.
-   *
-   * When this Ajax response is rendered, it will take care of generating the
-   * necessary Ajax commands, if any.
-   *
-   * @param array $attachments
-   *   An #attached array.
-   *
-   * @return $this
-   */
-  public function setAttachments(array $attachments) {
-    $this->attachments = $attachments;
-    return $this;
-  }
-
   /**
    * Add an AJAX command to the response.
    *
@@ -80,7 +58,7 @@ public function addCommand(CommandInterface $command, $prepend = FALSE) {
         'library' => $assets->getLibraries(),
         'drupalSettings' => $assets->getSettings(),
       ];
-      $attachments = BubbleableMetadata::mergeAttachments($this->attachments, $attachments);
+      $attachments = BubbleableMetadata::mergeAttachments($this->getAttachments(), $attachments);
       $this->setAttachments($attachments);
     }
 
@@ -97,147 +75,4 @@ public function &getCommands() {
     return $this->commands;
   }
 
-  /**
-   * {@inheritdoc}
-   *
-   * Sets the response's data to be the array of AJAX commands.
-   */
-  public function prepare(Request $request) {
-    $this->prepareResponse($request);
-    return $this;
-  }
-
-  /**
-   * Sets the rendered AJAX right before the response is prepared.
-   *
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The request object.
-   */
-  public function prepareResponse(Request $request) {
-    if ($this->data == '{}') {
-      $this->setData($this->ajaxRender($request));
-    }
-
-    // IE 9 does not support XHR 2 (http://caniuse.com/#feat=xhr2), so
-    // for that browser, jquery.form submits requests containing a file upload
-    // via an IFRAME rather than via XHR. Since the response is being sent to
-    // an IFRAME, it must be formatted as HTML. Specifically:
-    // - It must use the text/html content type or else the browser will
-    //   present a download prompt. Note: This applies to both file uploads
-    //   as well as any ajax request in a form with a file upload form.
-    // - It must place the JSON data into a textarea to prevent browser
-    //   extensions such as Linkification and Skype's Browser Highlighter
-    //   from applying HTML transformations such as URL or phone number to
-    //   link conversions on the data values.
-    //
-    // Since this affects the format of the output, it could be argued that
-    // this should be implemented as a separate Accept MIME type. However,
-    // that would require separate variants for each type of AJAX request
-    // (e.g., drupal-ajax, drupal-dialog, drupal-modal), so for expediency,
-    // this browser workaround is implemented via a GET or POST parameter.
-    //
-    // @see http://malsup.com/jquery/form/#file-upload
-    // @see https://www.drupal.org/node/1009382
-    // @see https://www.drupal.org/node/2339491
-    // @see Drupal.ajax.prototype.beforeSend()
-    $accept = $request->headers->get('accept');
-
-    if (strpos($accept, 'text/html') !== FALSE) {
-      $this->headers->set('Content-Type', 'text/html; charset=utf-8');
-
-      // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
-      // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into
-      // links. This corrupts the JSON response. Protect the integrity of the
-      // JSON data by making it the value of a textarea.
-      // @see http://malsup.com/jquery/form/#file-upload
-      // @see https://www.drupal.org/node/1009382
-      $this->setContent('<textarea>' . $this->data  . '</textarea>');
-    }
-  }
-
-  /**
-   * Prepares the AJAX commands for sending back to the client.
-   *
-   * @param Request $request
-   *   The request object that the AJAX is responding to.
-   *
-   * @return array
-   *   An array of commands ready to be returned as JSON.
-   */
-  protected function ajaxRender(Request $request) {
-    $ajax_page_state = $request->request->get('ajax_page_state');
-
-    // Aggregate CSS/JS if necessary, but only during normal site operation.
-    $config = \Drupal::config('system.performance');
-    $optimize_css = !defined('MAINTENANCE_MODE') && $config->get('css.preprocess');
-    $optimize_js = !defined('MAINTENANCE_MODE') && $config->get('js.preprocess');
-
-    // Resolve the attached libraries into asset collections.
-    $assets = new AttachedAssets();
-    $assets->setLibraries(isset($this->attachments['library']) ? $this->attachments['library'] : [])
-      ->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])
-      ->setSettings(isset($this->attachments['drupalSettings']) ? $this->attachments['drupalSettings'] : []);
-    $asset_resolver = \Drupal::service('asset.resolver');
-    $css_assets = $asset_resolver->getCssAssets($assets, $optimize_css);
-    list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, $optimize_js);
-
-    // Render the HTML to load these files, and add AJAX commands to insert this
-    // HTML in the page. Settings are handled separately, afterwards.
-    $settings = [];
-    if (isset($js_assets_header['drupalSettings'])) {
-      $settings = $js_assets_header['drupalSettings']['data'];
-      unset($js_assets_header['drupalSettings']);
-    }
-    if (isset($js_assets_footer['drupalSettings'])) {
-      $settings = $js_assets_footer['drupalSettings']['data'];
-      unset($js_assets_footer['drupalSettings']);
-    }
-
-    // Prepend commands to add the assets, preserving their relative order.
-    $resource_commands = array();
-    $renderer = $this->getRenderer();
-    if (!empty($css_assets)) {
-      $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css_assets);
-      $resource_commands[] = new AddCssCommand($renderer->render($css_render_array));
-    }
-    if (!empty($js_assets_header)) {
-      $js_header_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_header);
-      $resource_commands[] = new PrependCommand('head', $renderer->render($js_header_render_array));
-    }
-    if (!empty($js_assets_footer)) {
-      $js_footer_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_footer);
-      $resource_commands[] = new AppendCommand('body', $renderer->render($js_footer_render_array));
-    }
-    foreach (array_reverse($resource_commands) as $resource_command) {
-      $this->addCommand($resource_command, TRUE);
-    }
-
-    // Prepend a command to merge changes and additions to drupalSettings.
-    if (!empty($settings)) {
-      // During Ajax requests basic path-specific settings are excluded from
-      // new drupalSettings values. The original page where this request comes
-      // from already has the right values. An Ajax request would update them
-      // with values for the Ajax request and incorrectly override the page's
-      // values.
-      // @see system_js_settings_alter()
-      unset($settings['path']);
-      $this->addCommand(new SettingsCommand($settings, TRUE), TRUE);
-    }
-
-    $commands = $this->commands;
-    \Drupal::moduleHandler()->alter('ajax_render', $commands);
-
-    return $commands;
-  }
-
-  /**
-   * The renderer service.
-   *
-   * @return \Drupal\Core\Render\Renderer
-   *   The renderer service.
-   */
-  protected function getRenderer() {
-    return \Drupal::service('renderer');
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
new file mode 100644
index 0000000000000000000000000000000000000000..a689b76ef94d799a4438d6c8c34c13e58a673144
--- /dev/null
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
@@ -0,0 +1,199 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\Ajax\AjaxResponseAttachmentsProcessor.
+ */
+
+namespace Drupal\Core\Ajax;
+
+use Drupal\Core\Asset\AssetCollectionRendererInterface;
+use Drupal\Core\Asset\AssetResolverInterface;
+use Drupal\Core\Asset\AttachedAssets;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Render\AttachmentsInterface;
+use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
+use Drupal\Core\Render\RendererInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Processes attachments of AJAX responses.
+ *
+ * @see \Drupal\Core\Ajax\AjaxResponse
+ * @see \Drupal\Core\Render\MainContent\AjaxRenderer
+ */
+class AjaxResponseAttachmentsProcessor implements AttachmentsResponseProcessorInterface {
+
+  /**
+   * The asset resolver service.
+   *
+   * @var \Drupal\Core\Asset\AssetResolverInterface
+   */
+  protected $assetResolver;
+
+  /**
+   * A config object for the system performance configuration.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
+   * The CSS asset collection renderer service.
+   *
+   * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
+   */
+  protected $cssCollectionRenderer;
+
+  /**
+   * The JS asset collection renderer service.
+   *
+   * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
+   */
+  protected $jsCollectionRenderer;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a AjaxResponseAttachmentsProcessor object.
+   *
+   * @param \Drupal\Core\Asset\AssetResolverInterface $asset_resolver
+   *   An asset resolver.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   A config factory for retrieving required config objects.
+   * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $css_collection_renderer
+   *   The CSS asset collection renderer.
+   * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $js_collection_renderer
+   *   The JS asset collection renderer.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler) {
+    $this->assetResolver = $asset_resolver;
+    $this->config = $config_factory->get('system.performance');
+    $this->cssCollectionRenderer = $css_collection_renderer;
+    $this->jsCollectionRenderer = $js_collection_renderer;
+    $this->requestStack = $request_stack;
+    $this->renderer = $renderer;
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processAttachments(AttachmentsInterface $response) {
+    // @todo Convert to assertion once https://www.drupal.org/node/2408013 lands
+    if (!$response instanceof AjaxResponse) {
+      throw new \InvalidArgumentException('\Drupal\Core\Ajax\AjaxResponse instance expected.');
+    }
+
+    $request = $this->requestStack->getCurrentRequest();
+
+    if ($response->getContent() == '{}') {
+      $response->setData($this->buildAttachmentsCommands($response, $request));
+    }
+
+    return $response;
+  }
+
+  /**
+   * Prepares the AJAX commands to attach assets.
+   *
+   * @param \Drupal\Core\Ajax\AjaxResponse $response
+   *   The AJAX response to update.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object that the AJAX is responding to.
+   *
+   * @return array
+   *   An array of commands ready to be returned as JSON.
+   */
+  protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) {
+    $ajax_page_state = $request->request->get('ajax_page_state');
+
+    // Aggregate CSS/JS if necessary, but only during normal site operation.
+    $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');
+    $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess');
+
+    $attachments = $response->getAttachments();
+
+    // Resolve the attached libraries into asset collections.
+    $assets = new AttachedAssets();
+    $assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : [])
+      ->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])
+      ->setSettings(isset($attachments['drupalSettings']) ? $attachments['drupalSettings'] : []);
+    $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css);
+    list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js);
+
+    // Render the HTML to load these files, and add AJAX commands to insert this
+    // HTML in the page. Settings are handled separately, afterwards.
+    $settings = [];
+    if (isset($js_assets_header['drupalSettings'])) {
+      $settings = $js_assets_header['drupalSettings']['data'];
+      unset($js_assets_header['drupalSettings']);
+    }
+    if (isset($js_assets_footer['drupalSettings'])) {
+      $settings = $js_assets_footer['drupalSettings']['data'];
+      unset($js_assets_footer['drupalSettings']);
+    }
+
+    // Prepend commands to add the assets, preserving their relative order.
+    $resource_commands = array();
+    if ($css_assets) {
+      $css_render_array = $this->cssCollectionRenderer->render($css_assets);
+      $resource_commands[] = new AddCssCommand($this->renderer->render($css_render_array));
+    }
+    if ($js_assets_header) {
+      $js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
+      $resource_commands[] = new PrependCommand('head', $this->renderer->render($js_header_render_array));
+    }
+    if ($js_assets_footer) {
+      $js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
+      $resource_commands[] = new AppendCommand('body', $this->renderer->render($js_footer_render_array));
+    }
+    foreach (array_reverse($resource_commands) as $resource_command) {
+      $response->addCommand($resource_command, TRUE);
+    }
+
+    // Prepend a command to merge changes and additions to drupalSettings.
+    if (!empty($settings)) {
+      // During Ajax requests basic path-specific settings are excluded from
+      // new drupalSettings values. The original page where this request comes
+      // from already has the right values. An Ajax request would update them
+      // with values for the Ajax request and incorrectly override the page's
+      // values.
+      // @see system_js_settings_alter()
+      unset($settings['path']);
+      $response->addCommand(new SettingsCommand($settings, TRUE), TRUE);
+    }
+
+    $commands = $response->getCommands();
+    $this->moduleHandler->alter('ajax_render', $commands);
+
+    return $commands;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/AjaxResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AjaxResponseSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..f35b8526a53eee080afb5e21d5aa2dd5305e064e
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/AjaxResponseSubscriber.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\AjaxResponseSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Response subscriber to handle AJAX responses.
+ */
+class AjaxResponseSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The AJAX response attachments processor service.
+   *
+   * @var \Drupal\Core\Render\AttachmentsResponseProcessorInterface
+   */
+  protected $ajaxResponseAttachmentsProcessor;
+
+  /**
+   * Constructs an AjaxResponseSubscriber object.
+   *
+   * @param \Drupal\Core\Render\AttachmentsResponseProcessorInterface $ajax_response_attachments_processor
+   *   The AJAX response attachments processor service.
+   */
+  public function __construct(AttachmentsResponseProcessorInterface $ajax_response_attachments_processor) {
+    $this->ajaxResponseAttachmentsProcessor = $ajax_response_attachments_processor;
+  }
+
+  /**
+   * Request parameter to indicate that a request is a Drupal Ajax request.
+   */
+  const AJAX_REQUEST_PARAMETER = '_drupal_ajax';
+
+  /**
+   * Sets the AJAX parameter from the current request.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   The response event, which contains the current request.
+   */
+  public function onRequest(GetResponseEvent $event) {
+    // Pass to the Html class that the current request is an Ajax request.
+    if ($event->getRequest()->request->get(static::AJAX_REQUEST_PARAMETER)) {
+      Html::setIsAjax(TRUE);
+    }
+  }
+
+  /**
+   * Renders the ajax commands right before preparing the result.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+   *   The response event, which contains the possible AjaxResponse object.
+   */
+  public function onResponse(FilterResponseEvent $event) {
+    $response = $event->getResponse();
+    if ($response instanceof AjaxResponse) {
+      $this->ajaxResponseAttachmentsProcessor->processAttachments($response);
+
+      // IE 9 does not support XHR 2 (http://caniuse.com/#feat=xhr2), so
+      // for that browser, jquery.form submits requests containing a file upload
+      // via an IFRAME rather than via XHR. Since the response is being sent to
+      // an IFRAME, it must be formatted as HTML. Specifically:
+      // - It must use the text/html content type or else the browser will
+      //   present a download prompt. Note: This applies to both file uploads
+      //   as well as any ajax request in a form with a file upload form.
+      // - It must place the JSON data into a textarea to prevent browser
+      //   extensions such as Linkification and Skype's Browser Highlighter
+      //   from applying HTML transformations such as URL or phone number to
+      //   link conversions on the data values.
+      //
+      // Since this affects the format of the output, it could be argued that
+      // this should be implemented as a separate Accept MIME type. However,
+      // that would require separate variants for each type of AJAX request
+      // (e.g., drupal-ajax, drupal-dialog, drupal-modal), so for expediency,
+      // this browser workaround is implemented via a GET or POST parameter.
+      //
+      // @see http://malsup.com/jquery/form/#file-upload
+      // @see https://www.drupal.org/node/1009382
+      // @see https://www.drupal.org/node/2339491
+      // @see Drupal.ajax.prototype.beforeSend()
+      $accept = $event->getRequest()->headers->get('accept');
+
+      if (strpos($accept, 'text/html') !== FALSE) {
+        $response->headers->set('Content-Type', 'text/html; charset=utf-8');
+
+        // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
+        // and Skype's Browser Highlighter, convert URLs, phone numbers, etc.
+        // into links. This corrupts the JSON response. Protect the integrity of
+        // the JSON data by making it the value of a textarea.
+        // @see http://malsup.com/jquery/form/#file-upload
+        // @see https://www.drupal.org/node/1009382
+        $response->setContent('<textarea>' . $response->getContent()  . '</textarea>');
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[KernelEvents::RESPONSE][] = array('onResponse', -100);
+    $events[KernelEvents::REQUEST][] = array('onRequest', 50);
+
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php
deleted file mode 100644
index 8a856590408f69f514120e7363ad40a11f7082ec..0000000000000000000000000000000000000000
--- a/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\EventSubscriber\AjaxSubscriber.
- */
-
-namespace Drupal\Core\EventSubscriber;
-
-use Drupal\Component\Utility\Html;
-use Drupal\Core\Ajax\AjaxResponse;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
-use Symfony\Component\HttpKernel\Event\GetResponseEvent;
-use Symfony\Component\HttpKernel\KernelEvents;
-
-/**
- * Subscribes to set AJAX HTML IDs and prepare AJAX responses.
- */
-class AjaxSubscriber implements EventSubscriberInterface {
-
-  /**
-   * Request parameter to indicate that a request is a Drupal Ajax request.
-   */
-  const AJAX_REQUEST_PARAMETER = '_drupal_ajax';
-
-  /**
-   * Sets the AJAX parameter from the current request.
-   *
-   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
-   *   The response event, which contains the current request.
-   */
-  public function onRequest(GetResponseEvent $event) {
-    // Pass to the Html class that the current request is an Ajax request.
-    if ($event->getRequest()->request->get(static::AJAX_REQUEST_PARAMETER)) {
-      Html::setIsAjax(TRUE);
-    }
-  }
-
-  /**
-   * Renders the ajax commands right before preparing the result.
-   *
-   * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
-   *   The response event, which contains the possible AjaxResponse object.
-   */
-  public function onResponse(FilterResponseEvent $event) {
-    $response = $event->getResponse();
-    if ($response instanceof AjaxResponse) {
-      $response->prepareResponse($event->getRequest());
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getSubscribedEvents() {
-    $events[KernelEvents::RESPONSE][] = array('onResponse', -100);
-    $events[KernelEvents::REQUEST][] = array('onRequest', 50);
-
-    return $events;
-  }
-
-}
diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
index 46d6c4605c550bad4a238516429703e66dd55ee6..5f9d4aca8d84a37a35f06b509cbf12d810c47aab 100644
--- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
@@ -129,8 +129,7 @@ protected function onHtml(GetResponseForExceptionEvent $event) {
     }
 
     $content = $this->t('The website encountered an unexpected error. Please try again later.');
-    $output = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Error'), 'maintenance_page');
-    $response = new Response($output);
+    $response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Error'), 'maintenance_page');
 
     if ($exception instanceof HttpExceptionInterface) {
       $response->setStatusCode($exception->getStatusCode());
diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlResponseSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..6fada86d857f7f0c6dc0360e5390a9493c817072
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/HtmlResponseSubscriber.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\HtmlResponseSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Render\HtmlResponse;
+use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Response subscriber to handle HTML responses.
+ */
+class HtmlResponseSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The HTML response attachments processor service.
+   *
+   * @var \Drupal\Core\Render\AttachmentsResponseProcessorInterface
+   */
+  protected $htmlResponseAttachmentsProcessor;
+
+  /**
+   * Constructs a HtmlResponseSubscriber object.
+   *
+   * @param \Drupal\Core\Render\AttachmentsResponseProcessorInterface $html_response_attachments_processor
+   *   The HTML response attachments processor service.
+   */
+  public function __construct(AttachmentsResponseProcessorInterface $html_response_attachments_processor) {
+    $this->htmlResponseAttachmentsProcessor = $html_response_attachments_processor;
+  }
+
+  /**
+   * Processes attachments for HtmlResponse responses.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+   *   The event to process.
+   */
+  public function onRespond(FilterResponseEvent $event) {
+    if (!$event->isMasterRequest()) {
+      return;
+    }
+
+    $response = $event->getResponse();
+    if (!$response instanceof HtmlResponse) {
+      return;
+    }
+
+    $event->setResponse($this->htmlResponseAttachmentsProcessor->processAttachments($response));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[KernelEvents::RESPONSE][] = ['onRespond'];
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
index e43d00e57cd7a5f32c18e314521ea268e72537ae..92071da46df6cf5a40601adc373cecf4d6413ecd 100644
--- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
@@ -18,7 +18,6 @@
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
 use Symfony\Component\HttpKernel\KernelEvents;
 
@@ -107,8 +106,8 @@ public function onKernelRequestMaintenance(GetResponseEvent $event) {
         $content = Xss::filterAdmin(SafeMarkup::format($this->config->get('system.maintenance')->get('message'), array(
           '@site' => $this->config->get('system.site')->get('name'),
         )));
-        $output = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Site under maintenance'), 'maintenance_page');
-        $response = new Response($output, 503);
+        $response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Site under maintenance'), 'maintenance_page');
+        $response->setStatusCode(503);
         $event->setResponse($response);
       }
       else {
diff --git a/core/lib/Drupal/Core/Render/AttachmentsInterface.php b/core/lib/Drupal/Core/Render/AttachmentsInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d9b97c666e476de51049307c15afd3821936fbe
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/AttachmentsInterface.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\AttachmentsInterface.
+ */
+
+namespace Drupal\Core\Render;
+
+/**
+ * Defines an interface for responses that can expose #attached metadata.
+ *
+ * @todo If in Drupal 9, we remove attachments other than assets (libraries +
+ *   drupalSettings), then we can look into unifying this with
+ *   \Drupal\Core\Asset\AttachedAssetsInterface.
+ *
+ * @see \Drupal\Core\Render\AttachmentsTrait
+ */
+interface AttachmentsInterface {
+
+  /**
+   * Gets attachments.
+   *
+   * @return array
+   *   The attachments.
+   */
+  public function getAttachments();
+
+  /**
+   * Adds attachments.
+   *
+   * @param array $attachments
+   *   The attachments to add.
+   *
+   * @return $this
+   */
+  public function addAttachments(array $attachments);
+
+  /**
+   * Sets attachments.
+   *
+   * @param array $attachments
+   *   The attachments to set.
+   *
+   * @return $this
+   */
+  public function setAttachments(array $attachments);
+
+}
diff --git a/core/lib/Drupal/Core/Render/AttachmentsResponseProcessorInterface.php b/core/lib/Drupal/Core/Render/AttachmentsResponseProcessorInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..82577630f15f56b2c2451b3b3db345b5f3bcbeb3
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/AttachmentsResponseProcessorInterface.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\Render\AttachmentsResponseProcessorInterface.
+ */
+
+namespace Drupal\Core\Render;
+
+/**
+ * Defines an interface for processing attachments of responses that have them.
+ *
+ * @see \Drupal\Core\Render\HtmlResponse
+ * @see \Drupal\Core\Render\HtmlResponseAttachmentsProcessor
+ */
+interface AttachmentsResponseProcessorInterface {
+
+  /**
+   * Processes the attachments of a response that has attachments.
+   *
+   * @param \Drupal\Core\Render\AttachmentsInterface $response
+   *   The response to process the attachments for.
+   *
+   * @return \Drupal\Core\Render\AttachmentsInterface
+   *   The processed response.
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function processAttachments(AttachmentsInterface $response);
+
+}
diff --git a/core/lib/Drupal/Core/Render/AttachmentsTrait.php b/core/lib/Drupal/Core/Render/AttachmentsTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..9616e70479dca68e0e754179720acfdf689f5e22
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/AttachmentsTrait.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\Core\Render;
+
+/**
+ * Provides an implementation of AttachmentsInterface.
+ *
+ * @see \Drupal\Core\Render\AttachmentsInterface
+ */
+trait AttachmentsTrait {
+
+  /**
+   * The attachments for this response.
+   *
+   * @var array
+   */
+  protected $attachments = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAttachments() {
+    return $this->attachments;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addAttachments(array $attachments) {
+    $this->attachments = BubbleableMetadata::mergeAttachments($this->attachments, $attachments);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setAttachments(array $attachments) {
+    $this->attachments = $attachments;
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php
index c8012c6c8a9a23fc6d6cf93a324cb7a426d31c54..a7164d9f0b11496eb9251588c96641427c637e6b 100644
--- a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php
+++ b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php
@@ -19,14 +19,24 @@ class BareHtmlPageRenderer implements BareHtmlPageRendererInterface {
    */
   protected $renderer;
 
+  /**
+   * The HTML response attachments processor service.
+   *
+   * @var \Drupal\Core\Render\AttachmentsResponseProcessorInterface
+   */
+  protected $htmlResponseAttachmentsProcessor;
+
   /**
    * Constructs a new BareHtmlPageRenderer.
    *
    * @param \Drupal\Core\Render\RendererInterface $renderer
    *   The renderer service.
+   * @param \Drupal\Core\Render\AttachmentsResponseProcessorInterface $html_response_attachments_processor
+   *   The HTML response attachments processor service.
    */
-  public function __construct(RendererInterface $renderer) {
+  public function __construct(RendererInterface $renderer, AttachmentsResponseProcessorInterface $html_response_attachments_processor) {
     $this->renderer = $renderer;
+    $this->htmlResponseAttachmentsProcessor = $html_response_attachments_processor;
   }
 
   /**
@@ -55,16 +65,17 @@ public function renderBarePage(array $content, $title, $page_theme_property, arr
       $html['page']['highlighted'] = ['#type' => 'status_messages'];
     }
 
-    // We must first render the contents of the html.html.twig template, see
-    // \Drupal\Core\Render\MainContent\HtmlRenderer::renderResponse() for more
-    // information about this; the exact same pattern is used there and
-    // explained in detail there.
-    $this->renderer->render($html['page'], TRUE);
-
     // Add the bare minimum of attachments from the system module and the
     // current maintenance theme.
     system_page_attachments($html['page']);
-    return $this->renderer->render($html);
+    $this->renderer->renderRoot($html);
+
+    $response = new HtmlResponse();
+    $response->setContent($html);
+    // Process attachments, because this does not go via the regular render
+    // pipeline, but will be sent directly.
+    $response = $this->htmlResponseAttachmentsProcessor->processAttachments($response);
+    return $response;
   }
 
 }
diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php b/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php
index 1b4a5f538558ebdb6bce16e9c822cb27b76abaea..9e1f8ee4336e2e38255be08b189dfc2a292a4033 100644
--- a/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php
+++ b/core/lib/Drupal/Core/Render/BareHtmlPageRendererInterface.php
@@ -59,8 +59,8 @@ interface BareHtmlPageRendererInterface {
    *   Additional regions to add to the page. May also be used to pass the
    *   #show_messages property for #type 'page'.
    *
-   * @return string
-   *   The rendered HTML page.
+   * @return \Drupal\Core\Render\HtmlResponse
+   *   The rendered HTML response, ready to be sent.
    */
   public function renderBarePage(array $content, $title, $page_theme_property, array $page_additions = []);
 
diff --git a/core/lib/Drupal/Core/Render/BubbleableMetadata.php b/core/lib/Drupal/Core/Render/BubbleableMetadata.php
index 8e6875f51039bf7c1b5f22b84818c2c01a6d0637..5cb350e965a974d700e0de407e6f78e2780ca5f9 100644
--- a/core/lib/Drupal/Core/Render/BubbleableMetadata.php
+++ b/core/lib/Drupal/Core/Render/BubbleableMetadata.php
@@ -15,14 +15,9 @@
  *
  * @see \Drupal\Core\Render\RendererInterface::render()
  */
-class BubbleableMetadata extends CacheableMetadata {
+class BubbleableMetadata extends CacheableMetadata implements AttachmentsInterface {
 
-  /**
-   * Attached assets.
-   *
-   * @var string[][]
-   */
-  protected $attached = [];
+  use AttachmentsTrait;
 
   /**
    * Merges the values of another bubbleable metadata object with this one.
@@ -39,14 +34,14 @@ public function merge(CacheableMetadata $other) {
     // This is called many times per request, so avoid merging unless absolutely
     // necessary.
     if ($other instanceof BubbleableMetadata) {
-      if (empty($this->attached)) {
-        $result->attached = $other->attached;
+      if (empty($this->attachments)) {
+        $result->attachments = $other->attachments;
       }
-      elseif (empty($other->attached)) {
-        $result->attached = $this->attached;
+      elseif (empty($other->attachments)) {
+        $result->attachments = $this->attachments;
       }
       else {
-        $result->attached = static::mergeAttachments($this->attached, $other->attached);
+        $result->attachments = static::mergeAttachments($this->attachments, $other->attachments);
       }
     }
 
@@ -61,7 +56,7 @@ public function merge(CacheableMetadata $other) {
    */
   public function applyTo(array &$build) {
     parent::applyTo($build);
-    $build['#attached'] = $this->attached;
+    $build['#attached'] = $this->attachments;
   }
 
   /**
@@ -74,49 +69,10 @@ public function applyTo(array &$build) {
    */
   public static function createFromRenderArray(array $build) {
     $meta = parent::createFromRenderArray($build);
-    $meta->attached = (isset($build['#attached'])) ? $build['#attached'] : [];
+    $meta->attachments = (isset($build['#attached'])) ? $build['#attached'] : [];
     return $meta;
   }
 
-  /**
-   * Gets attachments.
-   *
-   * @return array
-   *   The attachments
-   */
-  public function getAttachments() {
-    return $this->attached;
-  }
-
-  /**
-   * Adds attachments.
-   *
-   * @param array $attachments
-   *   The attachments to add.
-   *
-   * @return $this
-   */
-  public function addAttachments(array $attachments) {
-    $other = (new BubbleableMetadata())->setAttachments($attachments);
-    $result = $other->merge($this);
-
-    $this->attached = $result->getAttachments();
-    return $this;
-  }
-
-  /**
-   * Sets attachments.
-   *
-   * @param array $attachments
-   *   The attachments to set.
-   *
-   * @return $this
-   */
-  public function setAttachments(array $attachments) {
-    $this->attached = $attachments;
-    return $this;
-  }
-
   /**
    * Gets assets.
    *
@@ -125,7 +81,7 @@ public function setAttachments(array $attachments) {
    * @deprecated Use ::getAttachments() instead. To be removed before Drupal 8.0.0.
    */
   public function getAssets() {
-    return $this->attached;
+    return $this->attachments;
   }
 
   /**
@@ -153,7 +109,7 @@ public function addAssets(array $assets) {
    * @deprecated Use ::setAttachments() instead. To be removed before Drupal 8.0.0.
    */
   public function setAssets(array $assets) {
-    $this->attached = $assets;
+    $this->attachments = $assets;
     return $this;
   }
 
diff --git a/core/lib/Drupal/Core/Render/HtmlResponse.php b/core/lib/Drupal/Core/Render/HtmlResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..7053f83e5293a59b56290d007885763bac8f7483
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/HtmlResponse.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\HtmlResponse.
+ */
+
+namespace Drupal\Core\Render;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\CacheableResponseInterface;
+use Drupal\Core\Cache\CacheableResponseTrait;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * A response that contains and can expose cacheability metadata and attachments.
+ *
+ * Supports Drupal's caching concepts: cache tags for invalidation and cache
+ * contexts for variations.
+ *
+ * Supports Drupal's idea of #attached metadata: libraries, settings, http_header and html_head.
+ *
+ * @see \Drupal\Core\Cache\CacheableResponse
+ * @see \Drupal\Core\Render\AttachmentsInterface
+ * @see \Drupal\Core\Render\AttachmentsTrait
+ */
+class HtmlResponse extends Response implements CacheableResponseInterface, AttachmentsInterface {
+
+  use CacheableResponseTrait;
+  use AttachmentsTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContent($content) {
+    // A render array can automatically be converted to a string and set the
+    // necessary metadata.
+    if (is_array($content) && (isset($content['#markup']))) {
+      $this->addCacheableDependency(CacheableMetadata::createFromRenderArray($content));
+      $this->setAttachments($content['#attached']);
+      $content = $content['#markup'];
+    }
+
+    parent::setContent($content);
+  }
+}
diff --git a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
new file mode 100644
index 0000000000000000000000000000000000000000..50ff584a077a152fe9c90f6ea03cfa9845881daa
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
@@ -0,0 +1,218 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\Render\HtmlResponseAttachmentsProcessor.
+ */
+
+namespace Drupal\Core\Render;
+
+use Drupal\Core\Asset\AssetCollectionRendererInterface;
+use Drupal\Core\Asset\AssetResolverInterface;
+use Drupal\Core\Asset\AttachedAssets;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Processes attachments of HTML responses.
+ *
+ * @see template_preprocess_html()
+ * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface
+ * @see \Drupal\Core\Render\BareHtmlPageRenderer
+ * @see \Drupal\Core\Render\HtmlResponse
+ * @see \Drupal\Core\Render\MainContent\HtmlRenderer
+ */
+class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorInterface {
+
+  /**
+   * The asset resolver service.
+   *
+   * @var \Drupal\Core\Asset\AssetResolverInterface
+   */
+  protected $assetResolver;
+
+  /**
+   * A config object for the system performance configuration.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
+   * The CSS asset collection renderer service.
+   *
+   * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
+   */
+  protected $cssCollectionRenderer;
+
+  /**
+   * The JS asset collection renderer service.
+   *
+   * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
+   */
+  protected $jsCollectionRenderer;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs a HtmlResponseAttachmentsProcessor object.
+   *
+   * @param \Drupal\Core\Asset\AssetResolverInterface $asset_resolver
+   *   An asset resolver.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   A config factory for retrieving required config objects.
+   * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $css_collection_renderer
+   *   The CSS asset collection renderer.
+   * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $js_collection_renderer
+   *   The JS asset collection renderer.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer.
+   */
+  public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer) {
+    $this->assetResolver = $asset_resolver;
+    $this->config = $config_factory->get('system.performance');
+    $this->cssCollectionRenderer = $css_collection_renderer;
+    $this->jsCollectionRenderer = $js_collection_renderer;
+    $this->requestStack = $request_stack;
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processAttachments(AttachmentsInterface $response) {
+    // @todo Convert to assertion once https://www.drupal.org/node/2408013 lands
+    if (!$response instanceof HtmlResponse) {
+      throw new \InvalidArgumentException('\Drupal\Core\Render\HtmlResponse instance expected.');
+    }
+
+    $attached = $response->getAttachments();
+
+    // Get the placeholders from attached and then remove them.
+    $placeholders = $attached['html_response_placeholders'];
+    unset($attached['html_response_placeholders']);
+
+    $variables = $this->processAssetLibraries($attached, $placeholders);
+
+    // Handle all non-asset attachments. This populates drupal_get_html_head()
+    // and drupal_get_http_header().
+    $all_attached = ['#attached' => $attached];
+    drupal_process_attached($all_attached);
+
+    // Get HTML head elements - if present.
+    if (isset($placeholders['head'])) {
+      $variables['head'] = drupal_get_html_head(FALSE);
+    }
+
+    // Now replace the placeholders in the response content with the real data.
+    $this->renderPlaceholders($response, $placeholders, $variables);
+
+    // Finally set the headers on the response.
+    $headers = drupal_get_http_header();
+    $this->setHeaders($response, $headers);
+
+    return $response;
+  }
+
+  /**
+   * Processes asset libraries into render arrays.
+   *
+   * @param array $attached
+   *   The attachments to process.
+   * @param array $placeholders
+   *   The placeholders that exist in the response.
+   *
+   * @return array
+   *   An array keyed by asset type, with keys:
+   *     - styles
+   *     - scripts
+   *     - scripts_bottom
+   */
+  protected function processAssetLibraries(array $attached, array $placeholders) {
+    $all_attached = ['#attached' => $attached];
+    $assets = AttachedAssets::createFromRenderArray($all_attached);
+
+    // Take Ajax page state into account, to allow for something like Turbolinks
+    // to be implemented without altering core.
+    // @see https://github.com/rails/turbolinks/
+    // @todo https://www.drupal.org/node/2497115 - Below line is broken due to ->request.
+    $ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state');
+    $assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []);
+
+    $variables = [];
+
+    // Print styles - if present.
+    if (isset($placeholders['styles'])) {
+      // Optimize CSS if necessary, but only during normal site operation.
+      $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');
+      $variables['styles'] = $this->cssCollectionRenderer->render($this->assetResolver->getCssAssets($assets, $optimize_css));
+    }
+
+    // Print scripts - if any are present.
+    if (isset($placeholders['scripts']) || isset($placeholders['scripts_bottom'])) {
+      // Optimize JS if necessary, but only during normal site operation.
+      $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess');
+      list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js);
+      $variables['scripts'] = $this->jsCollectionRenderer->render($js_assets_header);
+      $variables['scripts_bottom'] = $this->jsCollectionRenderer->render($js_assets_footer);
+    }
+
+    return $variables;
+  }
+
+  /**
+   * Renders variables into HTML markup and replaces placeholders in the
+   * response content.
+   *
+   * @param \Drupal\Core\Render\HtmlResponse $response
+   *   The HTML response to update.
+   * @param array $placeholders
+   *   An array of placeholders, keyed by type with the placeholders
+   *   present in the content of the response as values.
+   * @param array $variables
+   *   The variables to render and replace, keyed by type with renderable
+   *   arrays as values.
+   */
+  protected function renderPlaceholders(HtmlResponse $response, array $placeholders, array $variables) {
+    $content = $response->getContent();
+    foreach ($placeholders as $type => $placeholder) {
+      if (isset($variables[$type])) {
+        $content = str_replace($placeholder, $this->renderer->renderPlain($variables[$type]), $content);
+      }
+    }
+    $response->setContent($content);
+  }
+
+  /**
+   * Sets headers on a response object.
+   *
+   * @param \Drupal\Core\Render\HtmlResponse $response
+   *   The HTML response to update.
+   * @param array $headers
+   *   The headers to set.
+   */
+  protected function setHeaders(HtmlResponse $response, array $headers) {
+    foreach ($headers as $name => $value) {
+      // Drupal treats the HTTP response status code like a header, even though
+      // it really is not.
+      if ($name === 'status') {
+        $response->setStatusCode($value);
+      }
+      $response->headers->set($name, $value, FALSE);
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
index d61bb24d811bc92225fdf2be154e4fee0ba0ef63..cb987f47d0b29c0014935b0ab8632b459b5c69aa 100644
--- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -8,22 +8,27 @@
 namespace Drupal\Core\Render\MainContent;
 
 use Drupal\Component\Plugin\PluginManagerInterface;
-use Drupal\Core\Cache\CacheableMetadata;
-use Drupal\Core\Cache\CacheableResponse;
 use Drupal\Core\Controller\TitleResolverInterface;
 use Drupal\Core\Display\PageVariantInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Render\HtmlResponse;
 use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
 use Drupal\Core\Render\RenderCacheInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Render\RenderEvents;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Symfony\Component\DependencyInjection\ContainerAwareTrait;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Default main content renderer for HTML requests.
+ *
+ * For attachment handling of HTML responses:
+ * @see template_preprocess_html()
+ * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface
+ * @see \Drupal\Core\Render\BareHtmlPageRenderer
+ * @see \Drupal\Core\Render\HtmlResponse
+ * @see \Drupal\Core\Render\HtmlResponseAttachmentsProcessor
  */
 class HtmlRenderer implements MainContentRendererInterface {
 
@@ -119,39 +124,18 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
     // page.html.twig, hence add them here, just before rendering html.html.twig.
     $this->buildPageTopAndBottom($html);
 
-    // The three parts of rendered markup in html.html.twig (page_top, page and
-    // page_bottom) must be rendered with drupal_render_root(), so that their
-    // placeholders are replaced (which may attach additional assets).
-    // html.html.twig must be able to render the final list of attached assets,
-    // and hence may not replace any placeholders (because they might add yet
-    // more assets to be attached), and therefore it must be rendered with
-    // drupal_render(), not drupal_render_root().
-    $this->renderer->render($html['page'], TRUE);
-    if (isset($html['page_top'])) {
-      $this->renderer->render($html['page_top'], TRUE);
-    }
-    if (isset($html['page_bottom'])) {
-      $this->renderer->render($html['page_bottom'], TRUE);
-    }
-    $content = $this->renderer->render($html);
-
-    $response = new CacheableResponse($content, 200,[
-      'Content-Type' => 'text/html; charset=UTF-8',
-    ]);
-
-    // Bubble the cacheability metadata associated with the rendered render
-    // arrays to the response.
-    foreach (['page_top', 'page', 'page_bottom'] as $region) {
-      if (isset($html[$region])) {
-        $response->addCacheableDependency(CacheableMetadata::createFromRenderArray($html[$region]));
-      }
-    }
+    // @todo https://www.drupal.org/node/2495001 Make renderRoot return a
+    //       cacheable render array directly.
+    $this->renderer->renderRoot($html);
+    $content = $this->renderCache->getCacheableRenderArray($html);
 
     // Also associate the "rendered" cache tag. This allows us to invalidate the
     // entire render cache, regardless of the cache bin.
-    $default = new CacheableMetadata();
-    $default->setCacheTags(['rendered']);
-    $response->addCacheableDependency($default);
+    $content['#cache']['tags'][] = 'rendered';
+
+    $response = new HtmlResponse($content, 200, [
+      'Content-Type' => 'text/html; charset=UTF-8',
+    ]);
 
     return $response;
   }
diff --git a/core/modules/editor/src/Tests/QuickEditIntegrationTest.php b/core/modules/editor/src/Tests/QuickEditIntegrationTest.php
index ab482374686684edd85b07c74b861b40a28957c1..5a7b9cb82109d5c13b2a220858edbacb560d63d3 100644
--- a/core/modules/editor/src/Tests/QuickEditIntegrationTest.php
+++ b/core/modules/editor/src/Tests/QuickEditIntegrationTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\editor\Tests;
 
 use Drupal\Component\Serialization\Json;
+use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\quickedit\EditorSelector;
 use Drupal\quickedit\MetadataGenerator;
@@ -16,6 +17,8 @@
 use Drupal\quickedit_test\MockEditEntityFieldAccessCheck;
 use Drupal\editor\EditorController;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
 
 /**
  * Tests Edit module integration (Editor module's inline editing support).
@@ -214,7 +217,18 @@ public function testGetUntransformedTextCommand() {
         'data' => 'Test',
       )
     );
-    $this->assertEqual(Json::encode($expected), $response->prepare($request)->getContent(), 'The GetUntransformedTextCommand AJAX command works correctly.');
+
+    $ajax_response_attachments_processor = \Drupal::service('ajax_response.attachments_processor');
+    $subscriber = new AjaxResponseSubscriber($ajax_response_attachments_processor);
+    $event = new FilterResponseEvent(
+      \Drupal::service('http_kernel'),
+      $request,
+      HttpKernelInterface::MASTER_REQUEST,
+      $response
+    );
+    $subscriber->onResponse($event);
+
+    $this->assertEqual(Json::encode($expected), $response->getContent(), 'The GetUntransformedTextCommand AJAX command works correctly.');
   }
 
 }
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 4c2e4a68ae5101b7ee9d762def649e6fc25c37f9..6bd21a5c2376975f380e3a646085b7edd2dac7d1 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -19,7 +19,7 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\EventSubscriber\AjaxSubscriber;
+use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\Extension\MissingDependencyException;
 use Drupal\Core\Render\Element;
@@ -1801,7 +1801,7 @@ protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_p
         $extra_post[$key] = $value;
       }
     }
-    $extra_post[AjaxSubscriber::AJAX_REQUEST_PARAMETER] = 1;
+    $extra_post[AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER] = 1;
     $extra_post += $this->getAjaxPageStatePostData();
     // Now serialize all the $extra_post values, and prepend it with an '&'.
     $extra_post = '&' . $this->serializePostValues($extra_post);
diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php
index 3c10c8a58faa8b80f8cede442c91f89ca282f760..d207a42ab4948c6b09cddd88e775790dd5efda8f 100644
--- a/core/modules/system/src/Controller/DbUpdateController.php
+++ b/core/modules/system/src/Controller/DbUpdateController.php
@@ -198,7 +198,7 @@ public function handle($op, Request $request) {
     }
     $title = isset($output['#title']) ? $output['#title'] : $this->t('Drupal database update');
 
-    return new Response($this->bareHtmlPageRenderer->renderBarePage($output, $title, 'maintenance_page', $regions));
+    return $this->bareHtmlPageRenderer->renderBarePage($output, $title, 'maintenance_page', $regions);
   }
 
   /**
diff --git a/core/modules/system/src/Tests/Ajax/CommandsTest.php b/core/modules/system/src/Tests/Ajax/CommandsTest.php
index 969ec9c97b43fd87a5c6cadb5a81f5e4a7cac0c3..636c769178ae15a655cbdbb2dc155e03c51e10a5 100644
--- a/core/modules/system/src/Tests/Ajax/CommandsTest.php
+++ b/core/modules/system/src/Tests/Ajax/CommandsTest.php
@@ -23,7 +23,10 @@
 use Drupal\Core\Ajax\RemoveCommand;
 use Drupal\Core\Ajax\RestripeCommand;
 use Drupal\Core\Ajax\SettingsCommand;
+use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
 
 /**
  * Performs tests on AJAX framework commands.
@@ -133,7 +136,15 @@ public function testAttachedSettings() {
         'drupalSettings' => ['foo' => 'bar'],
       ]);
 
-      $response->prepare(new Request());
+      $ajax_response_attachments_processor = \Drupal::service('ajax_response.attachments_processor');
+      $subscriber = new AjaxResponseSubscriber($ajax_response_attachments_processor);
+      $event = new FilterResponseEvent(
+        \Drupal::service('http_kernel'),
+        new Request(),
+        HttpKernelInterface::MASTER_REQUEST,
+        $response
+      );
+      $subscriber->onResponse($event);
       $expected = [
         'command' => 'settings',
       ];
diff --git a/core/modules/views/src/Controller/ViewAjaxController.php b/core/modules/views/src/Controller/ViewAjaxController.php
index 6a5264cdf4a0c0a4ad67e267ea90e45ecb0e9527..6b7e1d5c32f032098973bc6c04584fc973c3f509 100644
--- a/core/modules/views/src/Controller/ViewAjaxController.php
+++ b/core/modules/views/src/Controller/ViewAjaxController.php
@@ -12,7 +12,7 @@
 use Drupal\Core\Ajax\ReplaceCommand;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\EventSubscriber\AjaxSubscriber;
+use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
 use Drupal\Core\Path\CurrentPathStack;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Routing\RedirectDestinationInterface;
@@ -134,7 +134,7 @@ public function ajaxView(Request $request) {
 
       // Remove all of this stuff from the query of the request so it doesn't
       // end up in pagers and tablesort URLs.
-      foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', AjaxSubscriber::AJAX_REQUEST_PARAMETER) as $key) {
+      foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER) as $key) {
         $request->query->remove($key);
         $request->request->remove($key);
       }
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index 1d65c589a80dc27c9c13f1a84d09fb2f4ba1844a..b321940751b449d17ac814bd0cf9765c4cc64bc6 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -301,38 +301,18 @@ function views_theme_suggestions_container_alter(array &$suggestions, array $var
 }
 
 /**
- * Implements hook_element_info_alter().
- *
- * @see views_page_display_pre_render()
- * @see views_preprocess_page()
- */
-function views_element_info_alter(&$types) {
-  $types['page']['#pre_render'][] = 'views_page_display_pre_render';
-}
-
-/**
- * #pre_render callback to set contextual links for views using a Page display.
+ * Implements MODULE_preprocess_HOOK().
  */
-function views_page_display_pre_render(array $element) {
+function views_preprocess_html(&$variables) {
   if (!\Drupal::moduleHandler()->moduleExists('contextual')) {
-    return $element;
+    return;
   }
+
   // If the main content of this page contains a view, attach its contextual
   // links to the overall page array. This allows them to be rendered directly
   // next to the page title.
   if ($render_array = Page::getPageRenderArray()) {
-    views_add_contextual_links($element, 'page', $render_array['#display_id'], $render_array);
-  }
-  return $element;
-}
-
-/**
- * Implements MODULE_preprocess_HOOK().
- */
-function views_preprocess_html(&$variables) {
-  // Early-return to prevent adding unnecessary JavaScript.
-  if (!\Drupal::moduleHandler()->moduleExists('contextual') || !\Drupal::currentUser()->hasPermission('access contextual links')) {
-    return;
+    views_add_contextual_links($variables['page'], 'page', $render_array['#display_id'], $render_array);
   }
 
   // If the page contains a view as its main content, contextual links may have
diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php
index c9050a42b110780a87f0ef8be6870e9f90197de4..23491f22ba166411077a1f09d22e0d7e63e369a3 100644
--- a/core/modules/views_ui/src/ViewUI.php
+++ b/core/modules/views_ui/src/ViewUI.php
@@ -11,7 +11,7 @@
 use Drupal\Component\Utility\Timer;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
-use Drupal\Core\EventSubscriber\AjaxSubscriber;
+use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Drupal\views\Views;
@@ -578,7 +578,7 @@ public function renderPreview($display_id, $args = array()) {
       // have some input in the query parameters, so we merge request() and
       // query() to ensure we get it all.
       $exposed_input = array_merge(\Drupal::request()->request->all(), \Drupal::request()->query->all());
-      foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', AjaxSubscriber::AJAX_REQUEST_PARAMETER, 'ajax_page_state', 'form_id', 'form_build_id', 'form_token') as $key) {
+      foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER, 'ajax_page_state', 'form_id', 'form_build_id', 'form_token') as $key) {
         if (isset($exposed_input[$key])) {
           unset($exposed_input[$key]);
         }
diff --git a/core/tests/Drupal/Tests/Core/Ajax/AjaxResponseTest.php b/core/tests/Drupal/Tests/Core/Ajax/AjaxResponseTest.php
index 19bbe8dd358a87adf75b62cda363e0fd0a32c1c8..acf3290b4a0366458532e130a507c93660dd9eed 100644
--- a/core/tests/Drupal/Tests/Core/Ajax/AjaxResponseTest.php
+++ b/core/tests/Drupal/Tests/Core/Ajax/AjaxResponseTest.php
@@ -8,9 +8,12 @@
 namespace Drupal\Tests\Core\Ajax;
 
 use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
 use Drupal\Core\Render\Element\Ajax;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
 
 /**
  * @coversDefaultClass \Drupal\Core\Ajax\AjaxResponse
@@ -81,7 +84,15 @@ public function testPrepareResponseForIeFormRequestsWithFileUpload() {
     $response = new AjaxResponse([]);
     $response->headers->set('Content-Type', 'application/json; charset=utf-8');
 
-    $response->prepare($request);
+    $ajax_response_attachments_processor = $this->getMock('\Drupal\Core\Render\AttachmentsResponseProcessorInterface');
+    $subscriber = new AjaxResponseSubscriber($ajax_response_attachments_processor);
+    $event = new FilterResponseEvent(
+      $this->getMock('\Symfony\Component\HttpKernel\HttpKernelInterface'),
+      $request,
+      HttpKernelInterface::MASTER_REQUEST,
+      $response
+    );
+    $subscriber->onResponse($event);
     $this->assertEquals('text/html; charset=utf-8', $response->headers->get('Content-Type'));
     $this->assertEquals($response->getContent(), '<textarea>[]</textarea>');
   }
diff --git a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
index 91c10c836ab094783b348af176b50965674fda10..52b6ef5033b50ebf1845760b1d9d6f6e72443bfe 100644
--- a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php
@@ -128,7 +128,7 @@ public function providerTestAddAttachments() {
     return [
       [new BubbleableMetadata(), [], new BubbleableMetadata()],
       [new BubbleableMetadata(), ['library' => ['core/foo']], (new BubbleableMetadata())->setAttachments(['library' => ['core/foo']])],
-      [(new BubbleableMetadata())->setAttachments(['library' => ['core/foo']]), ['library' => ['core/bar']], (new BubbleableMetadata())->setAttachments(['library' => ['core/bar', 'core/foo']])],
+      [(new BubbleableMetadata())->setAttachments(['library' => ['core/foo']]), ['library' => ['core/bar']], (new BubbleableMetadata())->setAttachments(['library' => ['core/foo', 'core/bar']])],
     ];
   }
 
diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine
index 9f8bc4a044bf0d1f180de6a580b499b69e5afbce..af5ebe7fdba05297c92cbb8674854c9602d0f00d 100644
--- a/core/themes/engines/twig/twig.engine
+++ b/core/themes/engines/twig/twig.engine
@@ -62,6 +62,16 @@ function twig_render_template($template_file, array $variables) {
   catch (\Twig_Error_Loader $e) {
     drupal_set_message($e->getMessage(), 'error');
   }
+  catch (\Twig_Error_Runtime $e) {
+    // In case there is a previous exception, re-throw the previous exception,
+    // so that the original exception is shown, rather than
+    // \Twig_Template::displayWithErrorHandling()'s exception.
+    $previous_exception = $e->getPrevious();
+    if ($previous_exception) {
+      throw $previous_exception;
+    }
+    throw $e;
+  }
   if ($twig_service->isDebug()) {
     $output['debug_prefix'] .= "\n\n<!-- THEME DEBUG -->";
     $output['debug_prefix'] .= "\n<!-- THEME HOOK: '" . SafeMarkup::checkPlain($variables['theme_hook_original']) . "' -->";