diff --git a/core/lib/Drupal/Core/Render/RenderCacheInterface.php b/core/lib/Drupal/Core/Render/RenderCacheInterface.php
index 0e24f1b..54eac73 100644
--- a/core/lib/Drupal/Core/Render/RenderCacheInterface.php
+++ b/core/lib/Drupal/Core/Render/RenderCacheInterface.php
@@ -42,7 +42,7 @@ public function getCacheableRenderArray(array $elements);
    * @param array $elements
    *   A renderable array.
    *
-   * @return array
+   * @return array|FALSE
    *   A renderable array, with the original element and all its children pre-
    *   rendered, or FALSE if no cached copy of the element is available.
    *
diff --git a/core/modules/aggregator/src/Plugin/views/row/Rss.php b/core/modules/aggregator/src/Plugin/views/row/Rss.php
index 72410e5..b34bb8f 100644
--- a/core/modules/aggregator/src/Plugin/views/row/Rss.php
+++ b/core/modules/aggregator/src/Plugin/views/row/Rss.php
@@ -79,7 +79,7 @@ public function render($row) {
       '#options' => $this->options,
       '#row' => $item,
     );
-    return drupal_render_root($build);
+    return $build;
   }
 
 }
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index c2a2ae6..b4c9cc4 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -211,6 +211,29 @@ function comment_node_links_alter(array &$node_links, NodeInterface $node, array
 }
 
 /**
+ * Implements hook_ENTITY_TYPE_view.
+ */
+function comment_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode) {
+  /** @var \Drupal\comment\CommentManagerInterface $comment_manager */
+  $comment_manager = \Drupal::service('comment.manager');
+  $fields = $comment_manager->getFields($entity->getEntityTypeId());
+  foreach ($fields as $field_name => $detail) {
+    if ($view_mode == 'rss' && $display->getComponent('links') && $entity->get($field_name)->status) {
+      // Add a comments RSS element which is a URL to the comments of this
+      // entity.
+      $options = array(
+        'fragment' => 'comments',
+        'absolute' => TRUE,
+      );
+      $entity->rss_elements[] = array(
+        'key' => 'comments',
+        'value' => $entity->url('canonical', $options),
+      );
+    }
+  }
+}
+
+/**
  * Implements hook_ENTITY_TYPE_view_alter() for node entities.
  */
 function comment_node_view_alter(array &$build, EntityInterface $node, EntityViewDisplayInterface $display) {
diff --git a/core/modules/comment/src/CommentLinkBuilder.php b/core/modules/comment/src/CommentLinkBuilder.php
index 951a69f..62ef270 100644
--- a/core/modules/comment/src/CommentLinkBuilder.php
+++ b/core/modules/comment/src/CommentLinkBuilder.php
@@ -89,19 +89,7 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
       if ($commenting_status != CommentItemInterface::HIDDEN) {
         // Entity has commenting status open or closed.
         $field_definition = $entity->getFieldDefinition($field_name);
-        if ($view_mode == 'rss') {
-          // Add a comments RSS element which is a URL to the comments of this
-          // entity.
-          $options = array(
-            'fragment' => 'comments',
-            'absolute' => TRUE,
-          );
-          $entity->rss_elements[] = array(
-            'key' => 'comments',
-            'value' => $entity->url('canonical', $options),
-          );
-        }
-        elseif ($view_mode == 'teaser') {
+        if ($view_mode == 'teaser') {
           // Teaser view: display the number of comments that have been posted,
           // or a link to add new comments if the user has permission, the
           // entity is open to new comments, and there currently are none.
@@ -155,7 +143,7 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
             }
           }
         }
-        else {
+        elseif ($view_mode !== 'rss') {
           // Entity in other view modes: add a "post comment" link if the user
           // is allowed to post comments and if this entity is allowing new
           // comments.
diff --git a/core/modules/comment/src/Plugin/views/row/Rss.php b/core/modules/comment/src/Plugin/views/row/Rss.php
index 1d8c341..3002765 100644
--- a/core/modules/comment/src/Plugin/views/row/Rss.php
+++ b/core/modules/comment/src/Plugin/views/row/Rss.php
@@ -84,7 +84,7 @@ public function render($row) {
       return;
     }
 
-    $item_text = '';
+    $description_build = [];
 
     $comment->link = $comment->url('canonical', array('absolute' => TRUE));
     $comment->rss_namespaces = array();
@@ -115,14 +115,16 @@ public function render($row) {
 
     if ($view_mode != 'title') {
       // We render comment contents.
-      $item_text .= drupal_render_root($build);
+      $description_build = $build;
     }
 
     $item = new \stdClass();
-    $item->description = $item_text;
+    $item->description = $description_build;
     $item->title = $comment->label();
     $item->link = $comment->link;
-    $item->elements = $comment->rss_elements;
+    // Provide a reference so that the render call in
+    // template_preprocess_views_view_row_rss() can still access it.
+    $item->elements = &$comment->rss_elements;
     $item->cid = $comment->id();
 
     $build = array(
@@ -131,7 +133,7 @@ public function render($row) {
       '#options' => $this->options,
       '#row' => $item,
     );
-    return drupal_render_root($build);
+    return $build;
   }
 
 }
diff --git a/core/modules/comment/src/Tests/CommentRssTest.php b/core/modules/comment/src/Tests/CommentRssTest.php
index ca3acda..e7072fa 100644
--- a/core/modules/comment/src/Tests/CommentRssTest.php
+++ b/core/modules/comment/src/Tests/CommentRssTest.php
@@ -8,6 +8,8 @@
 namespace Drupal\comment\Tests;
 
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
 
 /**
  * Tests comments as part of an RSS feed.
@@ -16,6 +18,8 @@
  */
 class CommentRssTest extends CommentTestBase {
 
+  use AssertPageCacheContextsAndTagsTrait;
+
   /**
    * Modules to install.
    *
@@ -24,6 +28,22 @@ class CommentRssTest extends CommentTestBase {
   public static $modules = array('views');
 
   /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Setup the rss view display.
+    EntityViewDisplay::create([
+      'status' => TRUE,
+      'targetEntityType' => 'node',
+      'bundle' => 'article',
+      'mode' => 'rss',
+      'content' => ['links' => ['weight' => 100]],
+    ])->save();
+  }
+
+  /**
    * Tests comments as part of an RSS feed.
    */
   function testCommentRss() {
@@ -31,6 +51,18 @@ function testCommentRss() {
     $this->drupalLogin($this->webUser);
     $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName());
     $this->drupalGet('rss.xml');
+
+    $this->assertCacheTags([
+      'config:views.view.frontpage', 'node:1', 'node_list', 'node_view', 'user:3',
+    ]);
+    $this->assertCacheContexts([
+      'languages:language_interface',
+      'theme',
+      'user.node_grants:view',
+      'user.permissions',
+      'timezone',
+    ]);
+
     $raw = '<comments>' . $this->node->url('canonical', array('fragment' => 'comments', 'absolute' => TRUE)) . '</comments>';
     $this->assertRaw($raw, 'Comments as part of RSS feed.');
 
diff --git a/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php b/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php
index 202f6a4..b53abbe 100644
--- a/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php
+++ b/core/modules/comment/tests/src/Unit/CommentLinkBuilderTest.php
@@ -146,18 +146,6 @@ public function testCommentLinkBuilder(NodeInterface $node, $context, $has_acces
     else {
       $this->assertSame($links, $expected);
     }
-    if ($context['view_mode'] == 'rss' && $node->get('comment')->status) {
-      $found = FALSE;
-      if ($node->get('comment')->status) {
-        foreach ($node->rss_elements as $element) {
-          if ($element['key'] == 'comments') {
-            $found = TRUE;
-            break;
-          }
-        }
-      }
-      $this->assertTrue($found);
-    }
   }
 
   /**
diff --git a/core/modules/node/config/optional/views.view.frontpage.yml b/core/modules/node/config/optional/views.view.frontpage.yml
index 2d9f093..8b51ebb 100644
--- a/core/modules/node/config/optional/views.view.frontpage.yml
+++ b/core/modules/node/config/optional/views.view.frontpage.yml
@@ -20,7 +20,7 @@ display:
         options:
           perm: 'access content'
       cache:
-        type: none
+        type: tag
         options: {  }
       empty:
         area_text_custom:
diff --git a/core/modules/node/src/Plugin/views/row/Rss.php b/core/modules/node/src/Plugin/views/row/Rss.php
index b97bb64..37261c1 100644
--- a/core/modules/node/src/Plugin/views/row/Rss.php
+++ b/core/modules/node/src/Plugin/views/row/Rss.php
@@ -111,7 +111,7 @@ public function render($row) {
       return;
     }
 
-    $item_text = '';
+    $description_build = [];
 
     $node->link = $node->url('canonical', array('absolute' => TRUE));
     $node->rss_namespaces = array();
@@ -154,22 +154,25 @@ public function render($row) {
 
     if ($display_mode != 'title') {
       // We render node contents.
-      $item_text .= drupal_render_root($build);
+      $description_build = $build;
     }
 
     $item = new \stdClass();
-    $item->description = SafeMarkup::set($item_text);
+    $item->description = $description_build;
     $item->title = $node->label();
     $item->link = $node->link;
-    $item->elements = $node->rss_elements;
+    // Provide a reference so that the render call in
+    // template_preprocess_views_view_row_rss() can still access it.
+    $item->elements = &$node->rss_elements;
     $item->nid = $node->id();
-    $theme_function = array(
+    $build = array(
       '#theme' => $this->themeFunctions(),
       '#view' => $this->view,
       '#options' => $this->options,
       '#row' => $item,
     );
-    return drupal_render_root($theme_function);
+
+    return $build;
   }
 
 }
diff --git a/core/modules/node/src/Tests/Views/FrontPageTest.php b/core/modules/node/src/Tests/Views/FrontPageTest.php
index 04414dc..b7e041b 100644
--- a/core/modules/node/src/Tests/Views/FrontPageTest.php
+++ b/core/modules/node/src/Tests/Views/FrontPageTest.php
@@ -11,7 +11,6 @@
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Url;
 use Drupal\node\Entity\Node;
-use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
 use Drupal\views\Tests\AssertViewsCacheTagsTrait;
 use Drupal\views\Tests\ViewTestBase;
 use Drupal\views\ViewExecutable;
@@ -24,7 +23,6 @@
  */
 class FrontPageTest extends ViewTestBase {
 
-  use AssertPageCacheContextsAndTagsTrait;
   use AssertViewsCacheTagsTrait;
 
   /**
@@ -293,6 +291,7 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) {
       'timezone',
     ]);
 
+    $this->pass('First page');
     // First page.
     $first_page_result_cache_tags = [
       'config:views.view.frontpage',
@@ -329,6 +328,7 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) {
     );
 
     // Second page.
+    $this->pass('second page');
     $this->assertPageCacheContextsAndTags(Url::fromRoute('view.frontpage.page_1', [], ['query' => ['page' => 1]]), $cache_contexts, [
       // The cache tags for the listed nodes.
       'node:1',
diff --git a/core/modules/node/src/Tests/Views/NodeLanguageTest.php b/core/modules/node/src/Tests/Views/NodeLanguageTest.php
index 16b2705..942f35b 100644
--- a/core/modules/node/src/Tests/Views/NodeLanguageTest.php
+++ b/core/modules/node/src/Tests/Views/NodeLanguageTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
 use Drupal\views\Plugin\views\PluginBase;
 use Drupal\views\Tests\ViewTestData;
 use Drupal\views\Views;
@@ -20,6 +21,8 @@
  */
 class NodeLanguageTest extends NodeTestBase {
 
+  use AssertPageCacheContextsAndTagsTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/rest/src/Plugin/views/display/RestExport.php b/core/modules/rest/src/Plugin/views/display/RestExport.php
index 32c0110..ba203ca 100644
--- a/core/modules/rest/src/Plugin/views/display/RestExport.php
+++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
@@ -273,28 +273,35 @@ public function collectRoutes(RouteCollection $collection) {
     }
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function execute() {
-    parent::execute();
+  public static function returnResponse($view_id, $display_id, array $args = []) {
+    $build = static::buildBasicRenderable($view_id, $display_id, $args);
 
-    $output = $this->view->render();
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
 
-    $header = [];
-    $header['Content-type'] = $this->getMimeType();
-
-    $response = new CacheableResponse($this->renderer->renderRoot($output), 200);
-    $cache_metadata = CacheableMetadata::createFromRenderArray($output);
+    $output = $renderer->renderRoot($build);
 
+    $response = new CacheableResponse($output, 200);
+    $cache_metadata = CacheableMetadata::createFromRenderArray($build);
     $response->addCacheableDependency($cache_metadata);
 
+    $response->headers->set('Content-type', $build['#content_type']);
+
     return $response;
   }
 
   /**
    * {@inheritdoc}
    */
+  public function execute() {
+    parent::execute();
+
+    return $this->view->render();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function render() {
     $build = array();
     $build['#markup'] = $this->view->style_plugin->render();
@@ -306,15 +313,10 @@ public function render() {
       $build['#suffix'] = '</pre>';
     }
 
-    // Defaults for bubbleable rendering metadata.
-    $build['#cache']['tags'] = isset($build['#cache']['tags']) ? $build['#cache']['tags'] : array();
-    $build['#cache']['max-age'] = isset($build['#cache']['max-age']) ? $build['#cache']['max-age'] : Cache::PERMANENT;
-
-    /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
-    $cache = $this->getPlugin('cache');
+    parent::renderSetCacheData($build);
 
-    $build['#cache']['tags'] = Cache::mergeTags($build['#cache']['tags'], $cache->getCacheTags());
-    $build['#cache']['max-age'] = Cache::mergeMaxAges($build['#cache']['max-age'], $cache->getCacheMaxAge());
+    $this->view->element['#content_type'] = $this->getMimeType();
+    $this->view->element['#cache_properties'][] = '#content_type';
 
     return $build;
   }
diff --git a/core/modules/rest/src/Plugin/views/style/Serializer.php b/core/modules/rest/src/Plugin/views/style/Serializer.php
index ba3ca6f..3fb5b5f 100644
--- a/core/modules/rest/src/Plugin/views/style/Serializer.php
+++ b/core/modules/rest/src/Plugin/views/style/Serializer.php
@@ -8,6 +8,7 @@
 namespace Drupal\rest\Plugin\views\style;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\views\Plugin\CacheablePluginInterface;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\style\StylePluginBase;
@@ -26,7 +27,7 @@
  *   display_types = {"data"}
  * )
  */
-class Serializer extends StylePluginBase {
+class Serializer extends StylePluginBase implements CacheablePluginInterface {
 
   /**
    * Overrides \Drupal\views\Plugin\views\style\StylePluginBase::$usesRowPlugin.
@@ -153,4 +154,18 @@ public function getFormats() {
     return $this->formats;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isCacheable() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts() {
+    return ['request_format'];
+  }
+
 }
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index ff382d5..f31ac53 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -261,29 +261,6 @@ protected function rebuildCache() {
   }
 
   /**
-   * Check if a HTTP response header exists and has the expected value.
-   *
-   * @param string $header
-   *   The header key, example: Content-Type
-   * @param string $value
-   *   The header value.
-   * @param string $message
-   *   (optional) A message to display with the assertion.
-   * @param string $group
-   *   (optional) The group this message is in, which is displayed in a column
-   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
-   *   translate this string. Defaults to 'Other'; most tests do not override
-   *   this default.
-   *
-   * @return bool
-   *   TRUE if the assertion succeeded, FALSE otherwise.
-   */
-  protected function assertHeader($header, $value, $message = '', $group = 'Browser') {
-    $header_value = $this->drupalGetHeader($header);
-    return $this->assertTrue($header_value == $value, $message ? $message : 'HTTP response header ' . $header . ' with value ' . $value . ' found.', $group);
-  }
-
-  /**
    * {@inheritdoc}
    *
    * This method is overridden to deal with a cURL quirk: the usage of
diff --git a/core/modules/rest/src/Tests/Views/StyleSerializerTest.php b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
index 0d16fcb..edeac92 100644
--- a/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
+++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
@@ -9,7 +9,10 @@
 
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Cache\Cache;
+use Drupal\entity_test\Entity\EntityTest;
 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
+use Drupal\views\Entity\View;
+use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Views;
 use Drupal\views\Tests\Plugin\PluginTestBase;
 use Drupal\views\Tests\ViewTestData;
@@ -79,6 +82,7 @@ public function testSerializerResponses() {
     $actual_json = $this->drupalGet('test/serialize/field', array(), array('Accept: application/json'));
     $this->assertResponse(200);
     $this->assertCacheTags($view->getCacheTags());
+    $this->assertCacheContexts(['languages:language_interface', 'theme', 'request_format']);
     // @todo Due to https://www.drupal.org/node/2352009 we can't yet test the
     // propagation of cache max-age.
 
@@ -135,6 +139,7 @@ public function testSerializerResponses() {
       $expected_cache_tags = Cache::mergeTags($expected_cache_tags, $entity->getCacheTags());
     }
     $this->assertCacheTags($expected_cache_tags);
+    $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
 
     $expected = $serializer->serialize($entities, 'hal_json');
     $actual_json = $this->drupalGet('test/serialize/entity', array(), array('Accept: application/hal+json'));
@@ -156,6 +161,7 @@ public function testSerializerResponses() {
     $expected = $serializer->serialize($entities, 'xml');
     $actual_xml = $this->drupalGet('test/serialize/entity');
     $this->assertIdentical($actual_xml, $expected, 'The expected XML output was found.');
+    $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
 
     // Allow multiple formats.
     $view->setDisplay('rest_export_1');
@@ -179,6 +185,111 @@ public function testSerializerResponses() {
   }
 
   /**
+   * Sets up a request onto the request stack with a specified format.
+   *
+   * @param string $format
+   *   The new request format.
+   */
+  protected function addRequestWithFormat($format) {
+    $request = \Drupal::request();
+    $request = clone $request;
+    $request->setRequestFormat($format);
+
+    \Drupal::requestStack()->push($request);
+  }
+
+  /**
+   * Tests REST export with views render caching enabled.
+   */
+  public function testRestRenderCaching() {
+    $this->drupalLogin($this->adminUser);
+    /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
+    $render_cache = \Drupal::service('render_cache');
+
+    // Enable render caching for the views.
+    /** @var \Drupal\views\ViewEntityInterface $storage */
+    $storage = View::load('test_serializer_display_entity');
+    $options = &$storage->getDisplay('default');
+    $options['display_options']['cache'] = [
+      'type' => 'tag',
+    ];
+    $storage->save();
+
+    $original = DisplayPluginBase::buildBasicRenderable('test_serializer_display_entity', 'rest_export_1');
+
+    // Ensure that there is no corresponding render cache item yet.
+    $original['#cache'] += ['contexts' => []];
+    $original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
+
+    $cache_tags = [
+      'config:views.view.test_serializer_display_entity',
+      'entity_test:1',
+      'entity_test:10',
+      'entity_test:2',
+      'entity_test:3',
+      'entity_test:4',
+      'entity_test:5',
+      'entity_test:6',
+      'entity_test:7',
+      'entity_test:8',
+      'entity_test:9',
+      'entity_test_list'
+    ];
+    $cache_contexts = [
+      'entity_test_view_grants',
+      'languages:language_interface',
+      'theme',
+      'request_format',
+    ];
+
+    $this->assertFalse($render_cache->get($original));
+
+    // Request the page, once in XML and once in JSON to ensure that the caching
+    // varies by it.
+    $result1 = $this->drupalGetJSON('test/serialize/entity');
+    $this->addRequestWithFormat('json');
+    $this->assertHeader('content-type', 'application/json');
+    $this->assertCacheContexts($cache_contexts);
+    $this->assertCacheTags($cache_tags);
+    $this->assertTrue($render_cache->get($original));
+
+    $result_xml = $this->drupalGet('test/serialize/entity', [], ['Accept: application/xml']);
+    $this->addRequestWithFormat('xml');
+    $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
+    $this->assertCacheContexts($cache_contexts);
+    $this->assertCacheTags($cache_tags);
+    $this->assertTrue($render_cache->get($original));
+
+    // Ensure that the XML output is different from the JSON one.
+    $this->assertNotEqual($result1, $result_xml);
+
+    // Ensure that the cached page works.
+    $result2 = $this->drupalGetJSON('test/serialize/entity');
+    $this->addRequestWithFormat('json');
+    $this->assertHeader('content-type', 'application/json');
+    $this->assertEqual($result2, $result1);
+    $this->assertCacheContexts($cache_contexts);
+    $this->assertCacheTags($cache_tags);
+    $this->assertTrue($render_cache->get($original));
+
+    // Create a new entity and ensure that the cache tags are taken over.
+    EntityTest::create(['name' => 'test_11', 'user_id' => $this->adminUser->id()])->save();
+    $result3 = $this->drupalGetJSON('test/serialize/entity');
+    $this->addRequestWithFormat('json');
+    $this->assertHeader('content-type', 'application/json');
+    $this->assertNotEqual($result3, $result2);
+
+    // Add the new entity cache tag and remove the first one, because we just
+    // show 10 items in total.
+    $cache_tags[] = 'entity_test:11';
+    unset($cache_tags[array_search('entity_test:1', $cache_tags)]);
+
+    $this->assertCacheContexts($cache_contexts);
+    $this->assertCacheTags($cache_tags);
+    $this->assertTrue($render_cache->get($original));
+  }
+
+  /**
    * Tests the response format configuration.
    */
   public function testResponseFormatConfiguration() {
@@ -192,9 +303,11 @@ public function testResponseFormatConfiguration() {
 
     // Should return a 406.
     $this->drupalGet('test/serialize/field', array(), array('Accept: application/json'));
+    $this->assertHeader('content-type', 'application/json');
     $this->assertResponse(406, 'A 406 response was returned when JSON was requested.');
      // Should return a 200.
     $this->drupalGet('test/serialize/field', array(), array('Accept: application/xml'));
+    $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
     $this->assertResponse(200, 'A 200 response was returned when XML was requested.');
 
     // Add 'json' as an accepted format, so we have multiple.
@@ -204,20 +317,29 @@ public function testResponseFormatConfiguration() {
     // Should return a 200.
     // @todo This should be fixed when we have better content negotiation.
     $this->drupalGet('test/serialize/field', array(), array('Accept: */*'));
+    $this->assertHeader('content-type', 'application/json');
     $this->assertResponse(200, 'A 200 response was returned when any format was requested.');
 
     // Should return a 200. Emulates a sample Firefox header.
     $this->drupalGet('test/serialize/field', array(), array('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'));
+    $this->assertHeader('content-type', 'application/json');
     $this->assertResponse(200, 'A 200 response was returned when a browser accept header was requested.');
 
     // Should return a 200.
     $this->drupalGet('test/serialize/field', array(), array('Accept: application/json'));
+    $this->assertHeader('content-type', 'application/json');
     $this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
+    $headers = $this->drupalGetHeaders();
+    $this->assertEqual($headers['content-type'], 'application/json', 'The header Content-type is correct.');
     // Should return a 200.
     $this->drupalGet('test/serialize/field', array(), array('Accept: application/xml'));
+    $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
     $this->assertResponse(200, 'A 200 response was returned when XML was requested');
+    $headers = $this->drupalGetHeaders();
+    $this->assertTrue(strpos($headers['content-type'], 'text/xml') !== FALSE, 'The header Content-type is correct.');
     // Should return a 406.
     $this->drupalGet('test/serialize/field', array(), array('Accept: application/html'));
+    $this->assertHeader('content-type', 'text/html; charset=UTF-8');
     $this->assertResponse(406, 'A 406 response was returned when HTML was requested.');
   }
 
@@ -368,7 +490,7 @@ public function testLivePreview() {
   }
 
   /**
-   * Tests the views interface for rest export displays.
+   * Tests the views interface for REST export displays.
    */
   public function testSerializerViewsUI() {
     $this->drupalLogin($this->adminUser);
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 721fdd9..d5d7fb0 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -2327,6 +2327,29 @@ protected function drupalGetHeader($name, $all_requests = FALSE) {
   }
 
   /**
+   * Check if a HTTP response header exists and has the expected value.
+   *
+   * @param string $header
+   *   The header key, example: Content-Type
+   * @param string $value
+   *   The header value.
+   * @param string $message
+   *   (optional) A message to display with the assertion.
+   * @param string $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Other'; most tests do not override
+   *   this default.
+   *
+   * @return bool
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertHeader($header, $value, $message = '', $group = 'Browser') {
+    $header_value = $this->drupalGetHeader($header);
+    return $this->assertTrue($header_value == $value, $message ? $message : 'HTTP response header ' . $header . ' with value ' . $value . ' found, actual value: ' . $header_value, $group);
+  }
+
+  /**
    * Gets an array containing all emails sent during this test case.
    *
    * @param $filter
@@ -2598,6 +2621,7 @@ protected function prepareRequestForGenerator($clean_urls = TRUE, $override_serv
     $server = array_merge($server, $override_server_vars);
 
     $request = Request::create($request_path, 'GET', array(), array(), array(), $server);
+    $request->server->set('REQUEST_TIME', REQUEST_TIME);
     $this->container->get('request_stack')->push($request);
 
     // The request context is normally set by the router_listener from within
diff --git a/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php b/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php
index 4893c54..494a6b8 100644
--- a/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php
+++ b/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php
@@ -76,9 +76,21 @@ protected function assertPageCacheContextsAndTags(Url $url, array $expected_cont
     $cache_entry = \Drupal::cache('render')->get($cid);
     sort($cache_entry->tags);
     $this->assertEqual($cache_entry->tags, $expected_tags);
-    if ($cache_entry->tags !== $expected_tags) {
-      debug('Missing cache tags: ' . implode(',', array_diff($cache_entry->tags, $expected_tags)));
-      debug('Unwanted cache tags: ' . implode(',', array_diff($expected_tags, $cache_entry->tags)));
+    $this->debugCacheTags($cache_entry->tags, $expected_tags);
+  }
+
+  /**
+   * Provides debug information for cache tags.
+   *
+   * @param string[] $actual_tags
+   *   The actual cache tags.
+   * @param string[] $expected_tags
+   *   The expected cache tags.
+   */
+  protected function debugCacheTags(array $actual_tags, array $expected_tags) {
+    if ($actual_tags !== $expected_tags) {
+      debug('Missing cache tags: ' . implode(',', array_diff($actual_tags, $expected_tags)));
+      debug('Unwanted cache tags: ' . implode(',', array_diff($expected_tags, $actual_tags)));
     }
   }
 
@@ -90,11 +102,10 @@ protected function assertPageCacheContextsAndTags(Url $url, array $expected_cont
    */
   protected function assertCacheTags(array $expected_tags) {
     $actual_tags = $this->getCacheHeaderValues('X-Drupal-Cache-Tags');
+    sort($expected_tags);
+    sort($actual_tags);
     $this->assertIdentical($actual_tags, $expected_tags);
-    if ($actual_tags !== $expected_tags) {
-      debug('Missing cache tags: ' . implode(',', array_diff($actual_tags, $expected_tags)));
-      debug('Unwanted cache tags: ' . implode(',', array_diff($expected_tags, $actual_tags)));
-    }
+    $this->debugCacheTags($actual_tags, $expected_tags);
   }
 
   /**
diff --git a/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php b/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php
index ede6410..53649f5 100644
--- a/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php
+++ b/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php
@@ -27,7 +27,9 @@ public static function getLabel() {
    * {@inheritdoc}
    */
   public function getContext() {
-    return hash('sha256', REQUEST_TIME);
+    // Return a constant value, so we can fetch render cache both in actual
+    // requests and test code itself.
+    return '299792458';
   }
 
 }
diff --git a/core/modules/taxonomy/src/Plugin/views/argument_default/Tid.php b/core/modules/taxonomy/src/Plugin/views/argument_default/Tid.php
index b439c06..526e4c3 100644
--- a/core/modules/taxonomy/src/Plugin/views/argument_default/Tid.php
+++ b/core/modules/taxonomy/src/Plugin/views/argument_default/Tid.php
@@ -211,13 +211,6 @@ public function getArgument() {
         }
       }
     }
-
-    // If the current page is a view that takes tid as an argument,
-    // find the tid argument and return it.
-    $views_page = views_get_page_view();
-    if ($views_page && isset($views_page->argument['tid'])) {
-      return $views_page->argument['tid']->argument;
-    }
   }
 
   /**
diff --git a/core/modules/user/src/Plugin/views/access/Permission.php b/core/modules/user/src/Plugin/views/access/Permission.php
index 4caab18..18f0275 100644
--- a/core/modules/user/src/Plugin/views/access/Permission.php
+++ b/core/modules/user/src/Plugin/views/access/Permission.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\user\PermissionHandlerInterface;
+use Drupal\views\Plugin\CacheablePluginInterface;
 use Drupal\views\Plugin\views\access\AccessPluginBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Routing\Route;
@@ -27,7 +28,7 @@
  *   help = @Translation("Access will be granted to users with the specified permission string.")
  * )
  */
-class Permission extends AccessPluginBase {
+class Permission extends AccessPluginBase implements CacheablePluginInterface {
 
   /**
    * Overrides Drupal\views\Plugin\Plugin::$usesOptions.
@@ -132,4 +133,18 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     );
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isCacheable() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts() {
+    return ['user.permissions'];
+  }
+
 }
diff --git a/core/modules/user/src/Plugin/views/access/Role.php b/core/modules/user/src/Plugin/views/access/Role.php
index 6a106de..9686d08 100644
--- a/core/modules/user/src/Plugin/views/access/Role.php
+++ b/core/modules/user/src/Plugin/views/access/Role.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\user\RoleStorageInterface;
+use Drupal\views\Plugin\CacheablePluginInterface;
 use Drupal\views\Plugin\views\access\AccessPluginBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Routing\Route;
@@ -26,7 +27,7 @@
  *   help = @Translation("Access will be granted to users with any of the specified roles.")
  * )
  */
-class Role extends AccessPluginBase {
+class Role extends AccessPluginBase implements CacheablePluginInterface {
 
   /**
    * Overrides Drupal\views\Plugin\Plugin::$usesOptions.
@@ -145,4 +146,19 @@ public function calculateDependencies() {
     return $dependencies;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isCacheable() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts() {
+    return ['user.roles'];
+  }
+
 }
+
diff --git a/core/modules/user/src/Plugin/views/argument_default/User.php b/core/modules/user/src/Plugin/views/argument_default/User.php
index 269be4e..16daae7 100644
--- a/core/modules/user/src/Plugin/views/argument_default/User.php
+++ b/core/modules/user/src/Plugin/views/argument_default/User.php
@@ -103,13 +103,6 @@ public function getArgument() {
         return $node->getOwnerId();
       }
     }
-
-    // If the current page is a view that takes uid as an argument.
-    $view = views_get_page_view();
-
-    if ($view && isset($view->argument['uid'])) {
-      return $view->argument['uid']->argument;
-    }
   }
 
   /**
diff --git a/core/modules/user/src/Tests/Views/AccessPermissionTest.php b/core/modules/user/src/Tests/Views/AccessPermissionTest.php
index aaa1daf..04b665a 100644
--- a/core/modules/user/src/Tests/Views/AccessPermissionTest.php
+++ b/core/modules/user/src/Tests/Views/AccessPermissionTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\user\Tests\Views;
 
 use Drupal\user\Plugin\views\access\Permission;
+use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Views;
 
 /**
@@ -40,4 +41,32 @@ function testAccessPerm() {
     $this->assertTrue($view->display_handler->access($this->normalUser));
   }
 
+  /**
+   * Tests access on render caching.
+   */
+  public function testRenderCaching() {
+    $view = Views::getView('test_access_perm');
+    $display = &$view->storage->getDisplay('default');
+    $display['display_options']['cache'] = [
+      'type' => 'tag',
+    ];
+
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    /** @var \Drupal\Core\Session\AccountSwitcherInterface $account_switcher */
+    $account_switcher = \Drupal::service('account_switcher');
+
+    $build = DisplayPluginBase::buildBasicRenderable('test_access_perm', 'default');
+
+    // First access as user without access, then with access.
+    $account_switcher->switchTo($this->normalUser);
+    $result = $renderer->renderPlain($build);
+    $this->assertNotEqual($result, '');
+
+    $build = DisplayPluginBase::buildBasicRenderable('test_access_perm', 'default');
+    $account_switcher->switchTo($this->webUser);
+    $result = $renderer->renderPlain($build);
+    $this->assertEqual($result, '');
+  }
+
 }
diff --git a/core/modules/user/src/Tests/Views/AccessRoleTest.php b/core/modules/user/src/Tests/Views/AccessRoleTest.php
index f79e86e..ae7af21 100644
--- a/core/modules/user/src/Tests/Views/AccessRoleTest.php
+++ b/core/modules/user/src/Tests/Views/AccessRoleTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\user\Tests\Views;
 
 use Drupal\user\Plugin\views\access\Role;
+use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Views;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -92,4 +93,36 @@ function testAccessRole() {
     $this->assertResponse(200);
   }
 
+  /**
+   * Tests access on render caching.
+   */
+  public function testRenderCaching() {
+    $view = Views::getView('test_access_role');
+    $display = &$view->storage->getDisplay('default');
+    $display['display_options']['cache'] = [
+      'type' => 'tag',
+    ];
+    $display['display_options']['access']['options']['role'] = array(
+      $this->normalRole => $this->normalRole,
+    );
+    $view->save();
+
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    /** @var \Drupal\Core\Session\AccountSwitcherInterface $account_switcher */
+    $account_switcher = \Drupal::service('account_switcher');
+
+    $build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default');
+
+    // First access as user without access, then with access.
+    $account_switcher->switchTo($this->normalUser);
+    $result = $renderer->renderPlain($build);
+    $this->assertNotEqual($result, '');
+
+    $build = DisplayPluginBase::buildBasicRenderable('test_access_role', 'default');
+    $account_switcher->switchTo($this->webUser);
+    $result = $renderer->renderPlain($build);
+    $this->assertEqual($result, '');
+  }
+
 }
diff --git a/core/modules/views/src/Element/View.php b/core/modules/views/src/Element/View.php
index 9c11d3b..676f6d0 100644
--- a/core/modules/views/src/Element/View.php
+++ b/core/modules/views/src/Element/View.php
@@ -23,7 +23,6 @@ class View extends RenderElement {
   public function getInfo() {
     $class = get_class($this);
     return array(
-      '#theme_wrappers' => array('container'),
       '#pre_render' => array(
         array($class, 'preRenderViewElement'),
       ),
@@ -31,6 +30,7 @@ public function getInfo() {
       '#display_id' => 'default',
       '#arguments' => array(),
       '#embed' => TRUE,
+      '#cache' => [],
     );
   }
 
@@ -38,8 +38,6 @@ public function getInfo() {
    * View element pre render callback.
    */
   public static function preRenderViewElement($element) {
-    $element['#attributes']['class'][] = 'views-element-container';
-
     if (!isset($element['#view'])) {
       $view = Views::getView($element['#name']);
     }
@@ -47,6 +45,17 @@ public static function preRenderViewElement($element) {
       $view = $element['#view'];
     }
 
+    $view->element = &$element;
+    // Mark the element as being prerendered, so other code like
+    // \Drupal\views\ViewExecutable::setCurrentPage knows that its no longer
+    // possible to manipulate the $element.
+    $view->element['#pre_rendered'] = TRUE;
+
+
+    if (isset($element['#response'])) {
+      $view->setResponse($element['#response']);
+    }
+
     if ($view && $view->access($element['#display_id'])) {
       if (!empty($element['#embed'])) {
         $element += $view->preview($element['#display_id'], $element['#arguments']);
@@ -70,7 +79,18 @@ public static function preRenderViewElement($element) {
           $element['#title'] = &$element['view_build']['#title'];
         }
 
-        views_add_contextual_links($element, 'view', $view, $view->current_display);
+        if (empty($view->display_handler->getPluginDefinition()['returns_response'])) {
+          // views_add_contextual_links() needs the following information in
+          // order to be attached to the view.
+          $element['#view_id'] = $view->storage->id();
+          $element['#view_display_show_admin_links'] = $view->getShowAdminLinks();
+          $element['#view_display_plugin_id'] = $view->display_handler->getPluginId();
+          views_add_contextual_links($element, 'view', $view->current_display);
+        }
+      }
+      if (empty($view->display_handler->getPluginDefinition()['returns_response'])) {
+        $element['#attributes']['class'][] = 'views-element-container';
+        $element['#theme_wrappers'] = array('container');
       }
     }
 
diff --git a/core/modules/views/src/Plugin/Block/ViewsBlock.php b/core/modules/views/src/Plugin/Block/ViewsBlock.php
index 1e25133..8762c32 100644
--- a/core/modules/views/src/Plugin/Block/ViewsBlock.php
+++ b/core/modules/views/src/Plugin/Block/ViewsBlock.php
@@ -29,7 +29,7 @@ class ViewsBlock extends ViewsBlockBase {
   public function build() {
     $this->view->display_handler->preBlockBuild($this);
 
-    if ($output = $this->view->buildRenderable($this->displayID)) {
+    if ($output = $this->view->buildRenderable($this->displayID, [], FALSE)) {
       // Override the label to the dynamic title configured in the view.
       if (empty($this->configuration['views_label']) && $this->view->getTitle()) {
         $output['#title'] = Xss::filterAdmin($this->view->getTitle());
diff --git a/core/modules/views/src/Plugin/Block/ViewsBlockBase.php b/core/modules/views/src/Plugin/Block/ViewsBlockBase.php
index b87711f..853fe79 100644
--- a/core/modules/views/src/Plugin/Block/ViewsBlockBase.php
+++ b/core/modules/views/src/Plugin/Block/ViewsBlockBase.php
@@ -195,14 +195,20 @@ public function blockSubmit($form, FormStateInterface $form_state) {
   protected function addContextualLinks(&$output, $block_type = 'block') {
     // Do not add contextual links to an empty block.
     if (!empty($output)) {
+
       // Contextual links only work on blocks whose content is a renderable
       // array, so if the block contains a string of already-rendered markup,
       // convert it to an array.
       if (is_string($output)) {
         $output = array('#markup' => $output);
       }
-      // Add the contextual links.
-      views_add_contextual_links($output, $block_type, $this->view, $this->displayID);
+
+      // views_add_contextual_links() needs the following information in
+      // order to be attached to the view.
+      $output['#view_id'] = $this->view->storage->id();
+      $output['#view_display_show_admin_links'] = $this->view->getShowAdminLinks();
+      $output['#view_display_plugin_id'] = $this->view->display_handler->getPluginId();
+      views_add_contextual_links($output, $block_type, $this->displayID);
     }
   }
 
diff --git a/core/modules/views/src/Plugin/views/argument_default/QueryParameter.php b/core/modules/views/src/Plugin/views/argument_default/QueryParameter.php
index feccd21..b7407bb 100644
--- a/core/modules/views/src/Plugin/views/argument_default/QueryParameter.php
+++ b/core/modules/views/src/Plugin/views/argument_default/QueryParameter.php
@@ -95,7 +95,7 @@ public function isCacheable() {
    * {@inheritdoc}
    */
   public function getCacheContexts() {
-    return ['url'];
+    return ['url.query_args.' . $this->options['query_param']];
   }
 
 }
diff --git a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
index 23c917a..bd9b74e 100644
--- a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
+++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
@@ -171,7 +171,7 @@ protected function cacheExpire($type) {
    * @param $type
    *   The cache type, either 'query', 'result' or 'output'.
    */
-  protected function cacheSetExpire($type) {
+  protected function cacheSetMaxAge($type) {
     return Cache::PERMANENT;
   }
 
@@ -191,20 +191,8 @@ public function cacheSet($type) {
           'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
           'current_page' => $this->view->getCurrentPage(),
         );
-        \Drupal::cache($this->resultsBin)->set($this->generateResultsKey(), $data, $this->cacheSetExpire($type), $this->getCacheTags());
-        break;
-      case 'output':
-        // Make a copy of the output so it is not modified. If we render the
-        // display output directly an empty string will be returned when the
-        // view is actually rendered. If we try to set '#printed' to FALSE there
-        // are problems with asset bubbling.
-        $output = $this->view->display_handler->output;
-        $this->renderer->render($output);
-        // Also assign the cacheable render array back to the display handler so
-        // that is used to render the view for this request and rendering does
-        // not happen twice.
-        $this->storage = $this->view->display_handler->output = $this->renderCache->getCacheableRenderArray($output);
-        \Drupal::cache($this->outputBin)->set($this->generateOutputKey(), $this->storage, $this->cacheSetExpire($type), Cache::mergeTags($this->storage['#cache']['tags'], ['rendered']));
+        $expire = ($this->cacheSetMaxAge($type) === Cache::PERMANENT) ? Cache::PERMANENT : (int) $this->view->getRequest()->server->get('REQUEST_TIME') + $this->cacheSetMaxAge($type);
+        \Drupal::cache($this->resultsBin)->set($this->generateResultsKey(), $data, $expire, $this->getCacheTags());
         break;
     }
   }
@@ -235,18 +223,6 @@ public function cacheGet($type) {
           }
         }
         return FALSE;
-      case 'output':
-        if ($cache = \Drupal::cache($this->outputBin)->get($this->generateOutputKey())) {
-          if (!$cutoff || $cache->created > $cutoff) {
-            $this->storage = $cache->data;
-            $this->view->display_handler->output = $this->storage;
-            $this->view->element['#attached'] = &$this->view->display_handler->output['#attached'];
-            $this->view->element['#cache']['tags'] = &$this->view->display_handler->output['#cache']['tags'];
-            $this->view->element['#post_render_cache'] = &$this->view->display_handler->output['#post_render_cache'];
-            return TRUE;
-          }
-        }
-        return FALSE;
     }
   }
 
@@ -280,11 +256,6 @@ public function cacheFlush() {
   public function postRender(&$output) { }
 
   /**
-   * Start caching the html head.
-   */
-  public function cacheStart() { }
-
-  /**
    * Calculates and sets a cache ID used for the result cache.
    *
    * @return string
@@ -326,30 +297,6 @@ public function generateResultsKey() {
   }
 
   /**
-   * Calculates and sets a cache ID used for the output cache.
-   *
-   * @return string
-   *   The generated cache ID.
-   */
-  public function generateOutputKey() {
-    if (!isset($this->outputKey)) {
-      $user = \Drupal::currentUser();
-      $key_data = array(
-        'result' => $this->view->result,
-        'roles' => $user->getRoles(),
-        'super-user' => $user->id() == 1, // special caching for super user.
-        'theme' => \Drupal::theme()->getActiveTheme()->getName(),
-        'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId(),
-        'base_url' => $GLOBALS['base_url'],
-      );
-
-      $this->outputKey = $this->view->storage->id() . ':' . $this->displayHandler->display['id'] . ':output:' . hash('sha256', serialize($key_data));
-    }
-
-    return $this->outputKey;
-  }
-
-  /**
    * Gets an array of cache tags for the current view.
    *
    * @return string[]
@@ -359,7 +306,7 @@ public function getCacheTags() {
     $tags = $this->view->storage->getCacheTags();
 
     // The list cache tags for the entity types listed in this view.
-    $entity_information = $this->view->query->getEntityTableInfo();
+    $entity_information = $this->view->getQuery()->getEntityTableInfo();
 
     if (!empty($entity_information)) {
       // Add the list cache tags for each entity type used by this view.
diff --git a/core/modules/views/src/Plugin/views/cache/None.php b/core/modules/views/src/Plugin/views/cache/None.php
index 4ef83b5..863cfe3 100644
--- a/core/modules/views/src/Plugin/views/cache/None.php
+++ b/core/modules/views/src/Plugin/views/cache/None.php
@@ -20,9 +20,6 @@
  */
 class None extends CachePluginBase {
 
-  public function cacheStart() {
-  }
-
   public function summaryTitle() {
     return $this->t('None');
   }
diff --git a/core/modules/views/src/Plugin/views/cache/Time.php b/core/modules/views/src/Plugin/views/cache/Time.php
index 37c6cdd..3925815 100644
--- a/core/modules/views/src/Plugin/views/cache/Time.php
+++ b/core/modules/views/src/Plugin/views/cache/Time.php
@@ -101,7 +101,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
     parent::buildOptionsForm($form, $form_state);
     $options = array(60, 300, 1800, 3600, 21600, 518400);
     $options = array_map(array($this->dateFormatter, 'formatInterval'), array_combine($options, $options));
-    $options = array(-1 => $this->t('Never cache')) + $options + array('custom' => $this->t('Custom'));
+    $options = array(0 => $this->t('Never cache')) + $options + array('custom' => $this->t('Custom'));
 
     $form['results_lifespan'] = array(
       '#type' => 'select',
@@ -177,10 +177,13 @@ protected function cacheExpire($type) {
     }
   }
 
-  protected function cacheSetExpire($type) {
+  /**
+   * {@inheritdoc}
+   */
+  protected function cacheSetMaxAge($type) {
     $lifespan = $this->getLifespan($type);
     if ($lifespan) {
-      return time() + $lifespan;
+      return $lifespan;
     }
     else {
       return Cache::PERMANENT;
@@ -193,7 +196,7 @@ protected function cacheSetExpire($type) {
   protected function getDefaultCacheMaxAge() {
     // The max age, unless overridden by some other piece of the rendered code
     // is determined by the output time setting.
-    return $this->cacheSetExpire('output');
+    return (int) $this->cacheSetMaxAge('output');
   }
 
 }
diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
index 739d1aa..12cadeb 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -12,6 +12,8 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Plugin\PluginDependencyTrait;
@@ -2131,24 +2133,35 @@ public function render() {
       '#rows' => $rows,
       // Assigned by reference so anything added in $element['#attached'] will
       // be available on the view.
-      '#attached' => &$this->view->element['#attached'],
-      '#cache' => &$this->view->element['#cache'],
-      '#post_render_cache' => &$this->view->element['#post_render_cache'],
-    );
-
-    if (!isset($element['#cache'])) {
-      $element['#cache'] = [];
-    }
-    $element['#cache'] += ['tags' => []];
+      '#attached' => &$this->view->element['#attached'],    );
 
-    // If the output is a render array, add cache tags, regardless of whether
-    // caching is enabled or not; cache tags must always be set.
-    $element['#cache']['tags'] = Cache::mergeTags($element['#cache']['tags'], $this->view->getCacheTags());
+    $this->renderSetCacheData($this->view->element);
 
     return $element;
   }
 
   /**
+   * Sets up the render cache information onto a view render element
+   *
+   * @param array $element
+   *   The render element enhanced with cache tags, contexts and max age.
+   */
+  protected function renderSetCacheData(array &$element) {
+    /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
+    $cache = $this->getPlugin('cache');
+
+    $cacheability = new CacheableMetadata();
+    $cacheability->setCacheTags($this->view->getCacheTags());
+
+    $cacheability->setCacheContexts(isset($this->display['cache_metadata']['contexts']) ? $this->display['cache_metadata']['contexts'] : []);
+    $cacheability->setCacheMaxAge($cache->getCacheMaxAge());
+
+    $cacheability
+      ->merge(CacheableMetadata::createFromRenderArray($element))
+        ->applyTo($element);
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function elementPreRender(array $element) {
@@ -2314,18 +2327,52 @@ public function execute() { }
   /**
    * {@inheritdoc}
    */
-  public function buildRenderable(array $args = []) {
-    return [
+  public function buildRenderable(array $args = [], $cache = TRUE) {
+    $this->view->element += [
       '#type' => 'view',
       '#name' => $this->view->storage->id(),
       '#display_id' => $this->display['id'],
       '#arguments' => $args,
       '#embed' => FALSE,
       '#view' => $this->view,
+      '#cache_properties' => ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'],
+    ];
+
+    if ($cache) {
+      $this->view->element['#cache'] += ['keys' => []];
+      // Places like \Drupal\views\ViewExecutable::setCurrentPage() set up an
+      // additional cache context.
+      $this->view->element['#cache']['keys'] = array_merge(['views', 'display', $this->view->element['#name'], $this->view->element['#display_id']], $this->view->element['#cache']['keys']);
+
+      $this->renderSetCacheData($this->view->element);
+    }
+    else {
+      // Remove the cacheablity.
+      unset($this->view->element['#cache']['keys']);
+    }
+
+    return $this->view->element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function buildBasicRenderable($view_id, $display_id, array $args = []) {
+    $build = [
+      '#type' => 'view',
+      '#name' => $view_id,
+      '#display_id' => $display_id,
+      '#arguments' => $args,
+      '#embed' => FALSE,
       '#cache' => [
-        'contexts' => isset($this->display['cache_metadata']['contexts']) ?  $this->display['cache_metadata']['contexts'] : [],
+        'keys' => ['views', 'display', $view_id, $display_id],
       ],
     ];
+
+    $build['#cache_properties'] =  ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'];
+
+    return $build;
+
   }
 
   /**
diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
index b82de15..9f9c547 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginInterface.php
@@ -469,10 +469,15 @@ public function execute();
    * Note: This does not yet contain the executed view, but just the loaded view
    * executable.
    *
+   * @param array $args
+   *   (optional) Arguments of the view.
+   * @param bool $cache
+   *   (optional) Specify FALSE in order to opt out of render caching.
+   *
    * @return array
    *   The render array of a view.
    */
-  public function buildRenderable(array $args = []);
+  public function buildRenderable(array $args = [], $cache = TRUE);
 
   /**
    * Renders the display for the purposes of a live preview.
diff --git a/core/modules/views/src/Plugin/views/display/Feed.php b/core/modules/views/src/Plugin/views/display/Feed.php
index baa5d6b..9e5ca26 100644
--- a/core/modules/views/src/Plugin/views/display/Feed.php
+++ b/core/modules/views/src/Plugin/views/display/Feed.php
@@ -8,6 +8,8 @@
 namespace Drupal\views\Plugin\views\display;
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\CacheableResponse;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Views;
@@ -67,25 +69,40 @@ protected function getType() {
     return 'feed';
   }
 
-  /**
-   * Overrides \Drupal\views\Plugin\views\display\PathPluginBase::execute().
-   */
-  public function execute() {
-    parent::execute();
+  public static function returnResponse($view_id, $display_id, array $args = []) {
+    $build = static::buildBasicRenderable($view_id, $display_id, $args);
 
-    $output = $this->view->render();
+    // Setup an empty response, so for example RSS can setup the proper
+    // Content-type header.
+    $response = new CacheableResponse('', 200);
+    $build['#response'] = $response;
+
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
+    $output = $renderer->renderRoot($build);
 
     if (empty($output)) {
       throw new NotFoundHttpException();
     }
 
-    $response = $this->view->getResponse();
-
-    $response->setContent(drupal_render_root($output));
+    $response->setContent($output);
+    $cache_metadata = CacheableMetadata::createFromRenderArray($build);
+    $response->addCacheableDependency($cache_metadata);
 
     return $response;
   }
 
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    parent::execute();
+
+    return $this->view->render();
+  }
+
   /**
    * Overrides \Drupal\views\Plugin\views\display\PathPluginBase::preview().
    */
@@ -107,7 +124,11 @@ public function preview() {
    * Overrides \Drupal\views\Plugin\views\display\PathPluginBase::render().
    */
   public function render() {
-    return $this->view->style_plugin->render($this->view->result);
+    $build = $this->view->style_plugin->render($this->view->result);
+
+    $this->renderSetCacheData($build);
+
+    return $build;
   }
 
   /**
diff --git a/core/modules/views/src/Plugin/views/display/Page.php b/core/modules/views/src/Plugin/views/display/Page.php
index 751b2fe..e66177e 100644
--- a/core/modules/views/src/Plugin/views/display/Page.php
+++ b/core/modules/views/src/Plugin/views/display/Page.php
@@ -15,6 +15,7 @@
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\Routing\Route;
 
 /**
  * The plugin that handles a full page.
@@ -35,6 +36,13 @@
 class Page extends PathPluginBase {
 
   /**
+   * The current page render array.
+   *
+   * @var array
+   */
+  protected static $pageRenderArray;
+
+  /**
    * Whether the display allows attachments.
    *
    * @var bool
@@ -84,6 +92,34 @@ public static function create(ContainerInterface $container, array $configuratio
   }
 
   /**
+   * Sets the current page views render array.
+   *
+   * @param array $element
+   *   (optional) An array array. If not specified the previous element is
+   *   returned.
+   *
+   * @return array
+   *   The page render array.
+   */
+  public static function &setPageRenderArray(array &$element = NULL) {
+    if (isset($element)) {
+      static::$pageRenderArray = &$element;
+    }
+
+    return static::$pageRenderArray;
+  }
+
+  /**
+   * Gets the current views page render array.
+   *
+   * @return array
+   *   A render array.
+   */
+  public static function &getPageRenderArray() {
+    return static::$pageRenderArray;
+  }
+
+  /**
    * Overrides \Drupal\views\Plugin\views\display\PathPluginBase::defineOptions().
    */
   protected function defineOptions() {
@@ -113,14 +149,26 @@ protected function defineOptions() {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public static function buildBasicRenderable($view_id, $display_id, array $args = [], Route $route = NULL) {
+    $build = parent::buildBasicRenderable($view_id, $display_id, $args);
+
+    if ($route) {
+      $build['#view_id'] = $route->getDefault('view_id');
+      $build['#view_display_plugin_id'] = $route->getOption('_view_display_plugin_id');
+      $build['#view_display_show_admin_links'] = $route->getOption('_view_display_show_admin_links');
+    }
+
+    return $build;
+  }
+
+  /**
    * Overrides \Drupal\views\Plugin\views\display\PathPluginBase::execute().
    */
   public function execute() {
     parent::execute();
 
-    // Let the world know that this is the page view we're using.
-    views_set_page_view($this->view);
-
     // And now render the view.
     $render = $this->view->render();
 
diff --git a/core/modules/views/src/Plugin/views/display/PathPluginBase.php b/core/modules/views/src/Plugin/views/display/PathPluginBase.php
index 415b704..d1aee85 100644
--- a/core/modules/views/src/Plugin/views/display/PathPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php
@@ -138,6 +138,7 @@ protected function getRoute($view_id, $display_id) {
       '_controller' => 'Drupal\views\Routing\ViewPageController::handle',
       'view_id' => $view_id,
       'display_id' => $display_id,
+      '_view_display_show_admin_links' => $this->getOption('show_admin_links'),
     );
 
     // @todo How do we apply argument validation?
@@ -161,6 +162,7 @@ protected function getRoute($view_id, $display_id) {
         // handler.
         $arg_id = 'arg_' . $arg_counter++;
         $bits[$pos] = '{' . $arg_id . '}';
+        $argument_map[$arg_id] = $arg_id;
       }
       elseif (strpos($bit, '%') === 0) {
         // Use the name defined in the path.
@@ -178,6 +180,7 @@ protected function getRoute($view_id, $display_id) {
       // In contrast to the previous loop add the defaults here, as % was not
       // specified, which means the argument is optional.
       $defaults[$arg_id] = NULL;
+      $argument_map[$arg_id] = $arg_id;
       $bits[] = $bit;
     }
 
@@ -203,6 +206,12 @@ protected function getRoute($view_id, $display_id) {
 
     // Set the argument map, in order to support named parameters.
     $route->setOption('_view_argument_map', $argument_map);
+    $route->setOption('_view_display_plugin_id', $this->getPluginId());
+    $route->setOption('_view_display_plugin_class', get_called_class());
+    $route->setOption('_view_display_show_admin_links', $this->getOption('show_admin_links'));
+
+    // Store whether the view will return a response.
+    $route->setOption('returns_response', !empty($this->getPluginDefinition()['returns_response']));
 
     return $route;
   }
diff --git a/core/modules/views/src/Plugin/views/row/RssFields.php b/core/modules/views/src/Plugin/views/row/RssFields.php
index b95b95f..1d1e48c 100644
--- a/core/modules/views/src/Plugin/views/row/RssFields.php
+++ b/core/modules/views/src/Plugin/views/row/RssFields.php
@@ -147,7 +147,8 @@ public function render($row) {
     // @todo Views should expect and store a leading /. See:
     //   https://www.drupal.org/node/2423913
     $item->link = Url::fromUserInput('/' . $this->getField($row_index, $this->options['link_field']))->setAbsolute()->toString();
-    $item->description = $this->getField($row_index, $this->options['description_field']);
+    $field = $this->getField($row_index, $this->options['description_field']);
+    $item->description = is_array($field) ? $field : ['#markup' => $field];
     $item->elements = array(
       array('key' => 'pubDate', 'value' => $this->getField($row_index, $this->options['date_field'])),
       array(
@@ -185,7 +186,8 @@ public function render($row) {
       '#row' => $item,
       '#field_alias' => isset($this->field_alias) ? $this->field_alias : '',
     );
-    return drupal_render_root($build);
+
+    return $build;
   }
 
   /**
diff --git a/core/modules/views/src/Plugin/views/sort/Random.php b/core/modules/views/src/Plugin/views/sort/Random.php
index 1cdfc03..6f682e5 100644
--- a/core/modules/views/src/Plugin/views/sort/Random.php
+++ b/core/modules/views/src/Plugin/views/sort/Random.php
@@ -26,6 +26,8 @@ public function usesGroupBy() {
 
   public function query() {
     $this->query->addOrderBy('rand');
+    // @todo Replace this once https://www.drupal.org/node/2464427 is in.
+    $this->view->element['#cache']['max-age'] = 0;
   }
 
   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
diff --git a/core/modules/views/src/Plugin/views/style/Rss.php b/core/modules/views/src/Plugin/views/style/Rss.php
index 274c5be..19726b2 100644
--- a/core/modules/views/src/Plugin/views/style/Rss.php
+++ b/core/modules/views/src/Plugin/views/style/Rss.php
@@ -109,7 +109,7 @@ public function render() {
       debug('Drupal\views\Plugin\views\style\Rss: Missing row plugin');
       return array();
     }
-    $rows = '';
+    $rows = [];
 
     // This will be filled in by the row plugin and is used later on in the
     // theming output.
@@ -126,14 +126,14 @@ public function render() {
 
     foreach ($this->view->result as $row_index => $row) {
       $this->view->row_index = $row_index;
-      $rows .= $this->view->rowPlugin->render($row);
+      $rows[] = $this->view->rowPlugin->render($row);
     }
 
     $build = array(
       '#theme' => $this->themeFunctions(),
       '#view' => $this->view,
       '#options' => $this->options,
-      '#rows' => SafeMarkup::set($rows),
+      '#rows' => $rows,
     );
     unset($this->view->row_index);
     return $build;
diff --git a/core/modules/views/src/Routing/ViewPageController.php b/core/modules/views/src/Routing/ViewPageController.php
index 4cd8b71..5af9298 100644
--- a/core/modules/views/src/Routing/ViewPageController.php
+++ b/core/modules/views/src/Routing/ViewPageController.php
@@ -11,7 +11,9 @@
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\views\Plugin\views\display\Page;
 use Drupal\views\ViewExecutableFactory;
+use Drupal\views\Views;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -65,31 +67,20 @@ public static function create(ContainerInterface $container) {
    *   The ID of the view
    * @param string $display_id
    *   The ID of the display.
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The request.
    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
    *   The route match.
    * @return null|void
    */
-  public function handle($view_id, $display_id, Request $request, RouteMatchInterface $route_match) {
-    $entity = $this->storage->load($view_id);
-    if (empty($entity)) {
-      throw new NotFoundHttpException(SafeMarkup::format('Page controller for view %id requested, but view was not found.', array('%id' => $view_id)));
-    }
-    $view = $this->executableFactory->get($entity);
-    $view->setRequest($request);
-    $view->setDisplay($display_id);
-    $view->initHandlers();
-
+  public function handle($view_id, $display_id, RouteMatchInterface $route_match) {
     $args = array();
-    $map = $route_match->getRouteObject()->getOption('_view_argument_map', array());
-    $arguments_length = count($view->argument);
-    for ($argument_index = 0; $argument_index < $arguments_length; $argument_index++) {
+    $route = $route_match->getRouteObject();
+    $map = $route->hasOption('_view_argument_map') ? $route->getOption('_view_argument_map') : array();
+
+    foreach ($map as $attribute => $parameter_name) {
       // Allow parameters be pulled from the request.
       // The map stores the actual name of the parameter in the request. Views
       // which override existing controller, use for example 'node' instead of
       // arg_nid as name.
-      $attribute = 'arg_' . $argument_index;
       if (isset($map[$attribute])) {
         $attribute = $map[$attribute];
       }
@@ -104,12 +95,16 @@ public function handle($view_id, $display_id, Request $request, RouteMatchInterf
       }
     }
 
-    $plugin_definition = $view->display_handler->getPluginDefinition();
-    if (!empty($plugin_definition['returns_response'])) {
-      return $view->executeDisplay($display_id, $args);
+    /** @var \Drupal\views\Plugin\views\display\DisplayPluginBase $class */
+    $class = $route->getOption('_view_display_plugin_class');
+    if ($route->getOption('returns_response')) {
+      return $class::returnResponse($view_id, $display_id, $args);
     }
     else {
-      return $view->buildRenderable($display_id, $args);
+      $build = $class::buildBasicRenderable($view_id, $display_id, $args, $route);
+      Page::setPageRenderArray($build);
+
+      return $build;
     }
   }
 
diff --git a/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php b/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php
index 1617175..eac7f0c 100644
--- a/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php
+++ b/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php
@@ -8,14 +8,19 @@
 namespace Drupal\views\Tests;
 
 use Drupal\Core\Cache\Cache;
+use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
+use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\ViewExecutable;
 use Symfony\Component\HttpFoundation\Request;
 
 trait AssertViewsCacheTagsTrait {
 
+  use AssertPageCacheContextsAndTagsTrait;
 
   /**
-   * Asserts a view's result & output cache items' cache tags.
+   * Asserts a view's result & render cache items' cache tags.
+   *
+   * This methods uses a full view object in order to render the view.
    *
    * @param \Drupal\views\ViewExecutable $view
    *   The view to test, must have caching enabled.
@@ -32,20 +37,29 @@
    *   The render array
    */
   protected function assertViewsCacheTags(ViewExecutable $view, $expected_results_cache, $views_caching_is_enabled, array $expected_render_array_cache_tags) {
-    $build = $view->preview();
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
+    $render_cache = \Drupal::service('render_cache');
+
+    $build = $view->buildRenderable();
+    $original = $build;
 
     // Ensure the current request is a GET request so that render caching is
     // active for direct rendering of views, just like for actual requests.
     /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
     $request_stack = \Drupal::service('request_stack');
-    $request_stack->push(new Request());
-    \Drupal::service('renderer')->renderRoot($build);
-    $request_stack->pop();
+    $request = new Request();
+    $request->server->set('REQUEST_TIME', REQUEST_TIME);
+    $view->setRequest($request);
+    $request_stack->push($request);
+    $renderer->renderRoot($build);
 
     // Render array cache tags.
     $this->pass('Checking render array cache tags.');
     sort($expected_render_array_cache_tags);
     $this->assertEqual($build['#cache']['tags'], $expected_render_array_cache_tags);
+    $this->debugCacheTags($build['#cache']['tags'], $expected_render_array_cache_tags);
 
     if ($views_caching_is_enabled) {
       $this->pass('Checking Views results cache item cache tags.');
@@ -53,35 +67,97 @@ protected function assertViewsCacheTags(ViewExecutable $view, $expected_results_
       $cache_plugin = $view->display_handler->getPlugin('cache');
 
       // Results cache.
+
+      // Ensure that the views query is built.
+      $view->build();
       $results_cache_item = \Drupal::cache('data')->get($cache_plugin->generateResultsKey());
       if (is_array($expected_results_cache)) {
         $this->assertTrue($results_cache_item, 'Results cache item found.');
         if ($results_cache_item) {
           sort($expected_results_cache);
           $this->assertEqual($results_cache_item->tags, $expected_results_cache);
+          $this->debugCacheTags($results_cache_item->tags, $expected_results_cache);
         }
       }
       else {
         $this->assertFalse($results_cache_item, 'Results cache item not found.');
       }
 
-      // Output cache.
-      $this->pass('Checking Views output cache item cache tags.');
-      $output_cache_item = \Drupal::cache('render')->get($cache_plugin->generateOutputKey());
+      $this->pass('Checking Views render cache item cache tags.');
+
+      $original['#cache'] += ['contexts' => []];
+      $original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
+
+      $render_cache_item = $render_cache->get($original);
       if ($views_caching_is_enabled === TRUE) {
-        $this->assertTrue($output_cache_item, 'Output cache item found.');
-        if ($output_cache_item) {
-          $this->assertEqual($output_cache_item->tags, Cache::mergeTags($expected_render_array_cache_tags, ['rendered']));
+        $this->assertTrue(!empty($render_cache_item), 'Render cache item found.');
+        if ($render_cache_item) {
+          $this->assertEqual($render_cache_item['#cache']['tags'], $expected_render_array_cache_tags);
+          $this->debugCacheTags($render_cache_item['#cache']['tags'], $expected_render_array_cache_tags);
         }
       }
       else {
-        $this->assertFalse($output_cache_item, 'Output cache item not found.');
+        $this->assertFalse($render_cache_item, 'Render cache item not found.');
       }
     }
 
     $view->destroy();
 
+    $request_stack->pop();
+
     return $build;
   }
 
+  /**
+   * Asserts a view's result & render cache items' cache tags.
+   *
+   * This method starts with a pre bubbling basic render array.
+   *
+   * @param \Drupal\views\ViewExecutable $view
+   *   The view.
+   * @param string[] $expected_render_array_cache_tags
+   *   The expected render cache tags.
+   * @param bool $views_caching_is_enabled
+   *   Defines whether views output / render caching is enabled.
+   */
+  protected function assertViewsCacheTagsFromStaticRenderArray(ViewExecutable $view, array $expected_render_array_cache_tags, $views_caching_is_enabled) {
+    $original = $build = DisplayPluginBase::buildBasicRenderable($view->id(), $view->current_display ?: 'default', $view->args);
+
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
+    $render_cache = \Drupal::service('render_cache');
+
+    // Ensure the current request is a GET request so that render caching is
+    // active for direct rendering of views, just like for actual requests.
+    /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
+    $request_stack = \Drupal::service('request_stack');
+    $request = new Request();
+    $request->server->set('REQUEST_TIME', REQUEST_TIME);
+    $request_stack->push($request);
+    $renderer->renderRoot($build);
+
+    // Render array cache tags.
+    $this->pass('Checking render array cache tags.');
+    sort($expected_render_array_cache_tags);
+    $this->assertEqual($build['#cache']['tags'], $expected_render_array_cache_tags);
+    $this->debugCacheTags($build['#cache']['tags'], $expected_render_array_cache_tags);
+
+    $this->pass('Checking Views render cache item cache tags.');
+    $original['#cache'] += ['contexts' => []];
+    $original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
+
+    $render_cache_item = $render_cache->get($original);
+    if ($views_caching_is_enabled) {
+      $this->assertTrue(!empty($render_cache_item), 'Render cache item found.');
+      if ($render_cache_item) {
+        $this->assertEqual($render_cache_item['#cache']['tags'], $expected_render_array_cache_tags);
+        $this->debugCacheTags($render_cache_item['#cache']['tags'], $expected_render_array_cache_tags);
+      }
+    }
+    else {
+      $this->assertFalse($render_cache_item, 'Render cache item not found.');
+    }
+  }
+
 }
diff --git a/core/modules/views/src/Tests/GlossaryTest.php b/core/modules/views/src/Tests/GlossaryTest.php
index 16e7c04..9b12759 100644
--- a/core/modules/views/src/Tests/GlossaryTest.php
+++ b/core/modules/views/src/Tests/GlossaryTest.php
@@ -10,7 +10,6 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Url;
-use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
 use Drupal\views\Views;
 
 /**
@@ -20,7 +19,6 @@
  */
 class GlossaryTest extends ViewTestBase {
 
-  use AssertPageCacheContextsAndTagsTrait;
   use AssertViewsCacheTagsTrait;
 
   /**
diff --git a/core/modules/views/src/Tests/Handler/SortRandomTest.php b/core/modules/views/src/Tests/Handler/SortRandomTest.php
index 843019b..f22a25e 100644
--- a/core/modules/views/src/Tests/Handler/SortRandomTest.php
+++ b/core/modules/views/src/Tests/Handler/SortRandomTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\views\Tests\Handler;
 
+use Drupal\Core\Cache\Cache;
+use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Tests\ViewUnitTestBase;
 use Drupal\views\Views;
 
@@ -26,10 +28,17 @@ class SortRandomTest extends ViewUnitTestBase {
 
   /**
    * Add more items to the test set, to make the order tests more robust.
+   *
+   * In total we have then 60 entries, which makes a probability of a collision
+   * of 1/60!, which is around 1/1E80, which is higher than the estimated amount
+   * of protons / electrons in the observable universe, also called the
+   * eddington number.
+   *
+   * @see http://en.wikipedia.org/wiki/Eddington_number
    */
   protected function dataSet() {
     $data = parent::dataSet();
-    for ($i = 0; $i < 50; $i++) {
+    for ($i = 0; $i < 55; $i++) {
       $data[] = array(
         'name' => 'name_' . $i,
         'age' => $i,
@@ -96,4 +105,39 @@ public function testRandomOrdering() {
     ));
   }
 
+  /**
+   * Tests random ordering with tags based caching.
+   *
+   * The random sorting should opt out of caching by defining a max age of 0.
+   */
+  public function testRandomOrderingWithRenderCaching() {
+    $view_random = $this->getBasicRandomView();
+
+    $display = &$view_random->storage->getDisplay('default');
+    $display['display_options']['cache'] = [
+      'type' => 'tag',
+    ];
+
+    $view_random->storage->save();
+
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
+    $render_cache = \Drupal::service('render_cache');
+
+    $original = $build = DisplayPluginBase::buildBasicRenderable($view_random->id(), 'default');
+    $result = $renderer->renderPlain($build);
+
+    $original['#cache'] += ['contexts' => []];
+    $original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
+
+    $this->assertFalse($render_cache->get($original), 'Ensure there is no render cache entry.');
+
+    $build = DisplayPluginBase::buildBasicRenderable($view_random->id(), 'default');
+    $result2 = $renderer->renderPlain($build);
+
+    // Ensure that the random ordering works and don't produce the same result.
+    $this->assertNotEqual($result, $result2);
+  }
+
 }
diff --git a/core/modules/views/src/Tests/Plugin/CacheTagTest.php b/core/modules/views/src/Tests/Plugin/CacheTagTest.php
index 34b2434..219857f 100644
--- a/core/modules/views/src/Tests/Plugin/CacheTagTest.php
+++ b/core/modules/views/src/Tests/Plugin/CacheTagTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\views\Tests\Plugin;
 
+use Drupal\Core\Cache\Cache;
+use Drupal\views\ViewExecutable;
 use Drupal\views\Views;
 
 /**
@@ -88,93 +90,128 @@ protected function setUp() {
     }
     $this->article = $this->drupalCreateNode(array('title' => "Test article", 'type' => 'article'));
     $this->user = $this->drupalCreateUser();
+
+    // Mark the current request safe, in order to make render cache working, see
+    // \Drupal\Core\Render\RenderCache::get
+    // @see https://www.drupal.org/node/2367555
+    \Drupal::request()->setMethod('GET');
+  }
+
+  /**
+   * Gets the render cache for a given view.
+   *
+   * @param \Drupal\views\ViewExecutable $view
+   *   The view.
+   *
+   * @return array|FALSE
+   *   The render cache result or FALSE if not existent.
+   */
+  protected function getRenderCache(ViewExecutable $view) {
+    /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
+    $render_cache = \Drupal::service('render_cache');
+    $view->element = ['#cache' => []];
+    $build = $view->buildRenderable();
+    $build['#cache']['contexts'] = Cache::mergeContexts($build['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
+
+    return $render_cache->get($build);
   }
 
   /**
    * Tests the tag cache plugin.
    */
   public function testTagCaching() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
     $view = Views::getView('test_tag_cache');
-    $view->render();
+    $build = $view->buildRenderable();
+    $renderer->renderPlain($build);
 
     // Saving the view should invalidate the tags.
     $cache_plugin = $view->display_handler->getPlugin('cache');
     $this->assertTrue($cache_plugin->cacheGet('results'), 'Results cache found.');
-    $this->assertTrue($cache_plugin->cacheGet('output'), 'Output cache found.');
+    $this->assertTrue($this->getRenderCache($view), 'Output cache found.');
 
     $view->storage->save();
 
     $this->assertFalse($cache_plugin->cacheGet('results'), 'Results cache empty after the view is saved.');
-    $this->assertFalse($cache_plugin->cacheGet('output'), 'Output cache empty after the view is saved.');
+    $this->assertFalse($this->getRenderCache($view), 'Output cache empty after the view is saved.');
 
     $view->destroy();
-    $view->render();
+    $build = $view->buildRenderable();
+    $renderer->renderPlain($build);
 
     // Test invalidating the nodes in this view invalidates the cache.
     $cache_plugin = $view->display_handler->getPlugin('cache');
     $this->assertTrue($cache_plugin->cacheGet('results'), 'Results cache found.');
-    $this->assertTrue($cache_plugin->cacheGet('output'), 'Output cache found.');
+    $this->assertTrue($this->getRenderCache($view), 'Output cache found.');
 
     $this->nodeViewBuilder->resetCache($this->pages);
 
     $this->assertFalse($cache_plugin->cacheGet('results'), 'Results cache empty after resetCache is called with pages.');
-    $this->assertFalse($cache_plugin->cacheGet('output'), 'Output cache empty after resetCache is called with pages.');
+    $this->assertFalse($this->getRenderCache($view), 'Output cache empty after resetCache is called with pages.');
 
     $view->destroy();
-    $view->render();
+    $build = $view->buildRenderable();
+    $renderer->renderPlain($build);
 
     // Test saving a node in this view invalidates the cache.
     $cache_plugin = $view->display_handler->getPlugin('cache');
     $this->assertTrue($cache_plugin->cacheGet('results'), 'Results cache found.');
-    $this->assertTrue($cache_plugin->cacheGet('output'), 'Output cache found.');
+    $this->assertTrue($this->getRenderCache($view), 'Output cache found.');
 
     $node = reset($this->pages);
     $node->save();
 
     $this->assertFalse($cache_plugin->cacheGet('results'), 'Results cache empty after a page node is saved.');
-    $this->assertFalse($cache_plugin->cacheGet('output'), 'Output cache empty after a page node is saved.');
+    $this->assertFalse($this->getRenderCache($view), 'Output cache empty after a page node is saved.');
 
     $view->destroy();
-    $view->render();
+    $build = $view->buildRenderable();
+    $renderer->renderPlain($build);
 
     // Test saving a node not in this view invalidates the cache too.
     $cache_plugin = $view->display_handler->getPlugin('cache');
     $this->assertTrue($cache_plugin->cacheGet('results'), 'Results cache found.');
-    $this->assertTrue($cache_plugin->cacheGet('output'), 'Output cache found.');
+    $this->assertTrue($this->getRenderCache($view), 'Output cache found.');
 
     $this->article->save();
 
     $this->assertFalse($cache_plugin->cacheGet('results'), 'Results cache empty after an article node is saved.');
-    $this->assertFalse($cache_plugin->cacheGet('output'), 'Output cache empty after an article node is saved.');
+    $this->assertFalse($this->getRenderCache($view), 'Output cache empty after an article node is saved.');
 
     $view->destroy();
-    $view->render();
+    $build = $view->buildRenderable();
+    $renderer->renderPlain($build);
 
     // Test that invalidating a tag for a user, does not invalidate the cache,
     // as the user entity type will not be contained in the views cache tags.
     $cache_plugin = $view->display_handler->getPlugin('cache');
     $this->assertTrue($cache_plugin->cacheGet('results'), 'Results cache found.');
-    $this->assertTrue($cache_plugin->cacheGet('output'), 'Output cache found.');
+    $this->assertTrue($this->getRenderCache($view), 'Output cache found.');
 
     $this->userViewBuilder->resetCache(array($this->user));
 
     $cache_plugin = $view->display_handler->getPlugin('cache');
     $this->assertTrue($cache_plugin->cacheGet('results'), 'Results cache found after a user is invalidated.');
-    $this->assertTrue($cache_plugin->cacheGet('output'), 'Output cache found after a user is invalidated.');
+    $this->assertTrue($this->getRenderCache($view), 'Output cache found after a user is invalidated.');
 
     $view->destroy();
-    $view->render();
+    // Invalidate the views cache tags in order to invalidate the render
+    // caching.
+    \Drupal::service('cache_tags.invalidator')->invalidateTags($view->storage->getCacheTags());
+    $build = $view->buildRenderable();
+    $renderer->renderPlain($build);
 
     // Test the cacheFlush method invalidates the cache.
     $cache_plugin = $view->display_handler->getPlugin('cache');
     $this->assertTrue($cache_plugin->cacheGet('results'), 'Results cache found.');
-    $this->assertTrue($cache_plugin->cacheGet('output'), 'Output cache found.');
+    $this->assertTrue($this->getRenderCache($view), 'Output cache found.');
 
     $cache_plugin->cacheFlush();
 
     $cache_plugin = $view->display_handler->getPlugin('cache');
     $this->assertFalse($cache_plugin->cacheGet('results'), 'Results cache empty after the cacheFlush() method is called.');
-    $this->assertFalse($cache_plugin->cacheGet('output'), 'Output cache empty after the cacheFlush() method is called.');
+    $this->assertFalse($this->getRenderCache($view), 'Output cache empty after the cacheFlush() method is called.');
   }
 
 }
diff --git a/core/modules/views/src/Tests/Plugin/CacheTest.php b/core/modules/views/src/Tests/Plugin/CacheTest.php
index 679a764..8db3e94 100644
--- a/core/modules/views/src/Tests/Plugin/CacheTest.php
+++ b/core/modules/views/src/Tests/Plugin/CacheTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\views\Tests\Plugin;
 
 use Drupal\node\Entity\Node;
+use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Tests\ViewUnitTestBase;
 use Drupal\views\Views;
 use Drupal\views_test_data\Plugin\views\filter\FilterTest as FilterPlugin;
@@ -43,6 +44,9 @@ protected function setUp($import_test_views = TRUE) {
     $this->installEntitySchema('node');
     $this->installEntitySchema('taxonomy_term');
     $this->installEntitySchema('user');
+
+    // Setup the current time properly.
+    \Drupal::request()->server->set('REQUEST_TIME', time());
   }
 
   /**
@@ -276,14 +280,18 @@ function testHeaderStorage() {
       )
     ));
 
-    $output = $view->preview();
-    \Drupal::service('renderer')->render($output);
+    $output = $view->buildRenderable();
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    $renderer->render($output);
+
     unset($view->pre_render_called);
     $view->destroy();
 
     $view->setDisplay();
-    $output = $view->preview();
-    \Drupal::service('renderer')->render($output);
+    $output = $view->buildRenderable();
+    $renderer->render($output);
+
     $this->assertTrue(in_array('views_test_data/test', $output['#attached']['library']), 'Make sure libraries are added for cached views.');
     $this->assertEqual(['foo' => 'bar'], $output['#attached']['drupalSettings'], 'Make sure drupalSettings are added for cached views.');
     // Note: views_test_data_views_pre_render() adds some cache tags.
diff --git a/core/modules/views/src/Tests/Plugin/CacheWebTest.php b/core/modules/views/src/Tests/Plugin/CacheWebTest.php
index 676faa3..85194e2 100644
--- a/core/modules/views/src/Tests/Plugin/CacheWebTest.php
+++ b/core/modules/views/src/Tests/Plugin/CacheWebTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\views\Tests\Plugin;
 
 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
+use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Views;
 
 /**
@@ -60,12 +61,15 @@ public function testCacheOutputOnPage() {
     $view->save();
     $this->container->get('router.builder')->rebuildIfNeeded();
 
-    $output_key = $view->getDisplay()->getPlugin('cache')->generateOutputKey();
-    $this->assertFalse(\Drupal::cache('render')->get($output_key));
+    /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
+    $render_cache = \Drupal::service('render_cache');
+    $cache_element = DisplayPluginBase::buildBasicRenderable('test_display', 'page_1');
+    $cache_element['#cache'] += ['contexts' => $this->container->getParameter('renderer.config')['required_cache_contexts']];
+    $this->assertFalse($render_cache->get($cache_element));
 
     $this->drupalGet('test-display');
     $this->assertResponse(200);
-    $this->assertTrue(\Drupal::cache('render')->get($output_key));
+    $this->assertTrue($render_cache->get($cache_element));
     $cache_tags = [
       'config:user.role.anonymous',
       'config:views.view.test_display',
@@ -76,7 +80,7 @@ public function testCacheOutputOnPage() {
 
     $this->drupalGet('test-display');
     $this->assertResponse(200);
-    $this->assertTrue(\Drupal::cache('render')->get($output_key));
+    $this->assertTrue($render_cache->get($cache_element));
     $this->assertCacheTags($cache_tags);
   }
 
diff --git a/core/modules/views/src/Tests/Plugin/DisplayPageWebTest.php b/core/modules/views/src/Tests/Plugin/DisplayPageWebTest.php
index 2da04f3..31bfd3c 100644
--- a/core/modules/views/src/Tests/Plugin/DisplayPageWebTest.php
+++ b/core/modules/views/src/Tests/Plugin/DisplayPageWebTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests\Plugin;
 
+use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
 use Drupal\views\Views;
 
 /**
@@ -16,6 +17,8 @@
  */
 class DisplayPageWebTest extends PluginTestBase {
 
+  use AssertPageCacheContextsAndTagsTrait;
+
   /**
    * Views used by this test.
    *
@@ -50,6 +53,7 @@ public function testArguments() {
 
     $this->drupalGet('test_route_with_argument/1');
     $this->assertResponse(200);
+    $this->assertCacheContexts(['languages:language_interface', 'theme', 'url']);
     $result = $this->xpath('//span[@class="field-content"]');
     $this->assertEqual(count($result), 1, 'Ensure that just the filtered entry was returned.');
     $this->assertEqual((string) $result[0], 1, 'The passed ID was returned.');
diff --git a/core/modules/views/src/Tests/Plugin/PagerKernelTest.php b/core/modules/views/src/Tests/Plugin/PagerKernelTest.php
index 0eb8f7d..71a1c0b 100644
--- a/core/modules/views/src/Tests/Plugin/PagerKernelTest.php
+++ b/core/modules/views/src/Tests/Plugin/PagerKernelTest.php
@@ -47,29 +47,26 @@ protected function setUp($import_test_views = TRUE) {
    */
   public function testSetPagerMethods() {
     $view = Views::getView('test_pager_full');
+
+    // Mark the view as cacheable in order have the cache checking working
+    // below.
+    $display = &$view->storage->getDisplay('default');
+    $display['display_options']['cache']['type'] = 'tag';
+    $view->storage->save();
+
     $output = $view->preview();
 
     \Drupal::service('renderer')->renderPlain($output);
     $this->assertIdentical(CacheBackendInterface::CACHE_PERMANENT, $output['#cache']['max-age']);
 
     foreach (['setItemsPerPage', 'setOffset', 'setCurrentPage'] as $method) {
-      // Without $keep_cacheablity.
       $view = Views::getView('test_pager_full');
       $view->setDisplay('default');
       $view->{$method}(1);
       $output = $view->preview();
 
       \Drupal::service('renderer')->renderPlain($output);
-      $this->assertIdentical(0, $output['#cache']['max-age'], 'Max age set to 0 without $keep_cacheablity.');
-
-      // With $keep_cacheablity.
-      $view = Views::getView('test_pager_full');
-      $view->setDisplay('default');
-      $view->{$method}(1, TRUE);
-      $output = $view->preview();
-
-      \Drupal::service('renderer')->renderPlain($output);
-      $this->assertIdentical(CacheBackendInterface::CACHE_PERMANENT, $output['#cache']['max-age'], 'Max age unchanged with $keep_cacheablity.');
+      $this->assertIdentical(CacheBackendInterface::CACHE_PERMANENT, $output['#cache']['max-age'], 'Max age kept.');
     }
 
   }
diff --git a/core/modules/views/src/Tests/RenderCacheIntegrationTest.php b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
index 4ec2c41..93fa2fa 100644
--- a/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
+++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php
@@ -92,6 +92,7 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
     // Empty result (no entities yet).
     $base_tags =  ['config:views.view.entity_test_fields', 'entity_test_list'];
     $this->assertViewsCacheTags($view, $base_tags, $do_assert_views_caches, $base_tags);
+    $this->assertViewsCacheTagsFromStaticRenderArray($view, $base_tags, $do_assert_views_caches);
 
 
     // Non-empty result (1 entity).
@@ -100,6 +101,7 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
 
     $tags_with_entity = Cache::mergeTags($base_tags, $entities[0]->getCacheTags());
     $this->assertViewsCacheTags($view, $tags_with_entity, $do_assert_views_caches, $tags_with_entity);
+    $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_with_entity, $do_assert_views_caches);
 
 
     // Paged result (more entities than the items-per-page limit).
@@ -108,27 +110,39 @@ protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) {
       $entity->save();
     }
     // Page 1.
+    $this->pass('Page 1');
+    \Drupal::request()->query->set('page', 0);
     $tags_page_1 = Cache::mergeTags($base_tags, $entities[1]->getCacheTags(), $entities[2]->getCacheTags(), $entities[3]->getCacheTags(), $entities[4]->getCacheTags(), $entities[5]->getCacheTags());
     $this->assertViewsCacheTags($view, $tags_page_1, $do_assert_views_caches, $tags_page_1);
+    $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_page_1, $do_assert_views_caches);
     $view->destroy();
     // Page 2.
+    $this->pass('Page 2');
     $view->setCurrentPage(1);
+    \Drupal::request()->query->set('page', 1);
     $tags_page_2 = Cache::mergeTags($base_tags, $entities[0]->getCacheTags());
     $this->assertViewsCacheTags($view, $tags_page_2, $do_assert_views_caches, $tags_page_2);
     $view->destroy();
 
     // Ensure that invalidation works on both pages.
+    $this->pass('Page invalidations');
+    $this->pass('Page 2');
     $view->setCurrentPage(1);
+    \Drupal::request()->query->set('page', 1);
     $entities[0]->name->value = $random_name = $this->randomMachineName();
     $entities[0]->save();
     $build = $this->assertViewsCacheTags($view, $tags_page_2, $do_assert_views_caches, $tags_page_2);
+    // $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_page_2, $do_assert_views_caches);
     $this->assertTrue(strpos($build['#markup'], $random_name) !== FALSE);
     $view->destroy();
 
+    $this->pass('Page 1');
     $view->setCurrentPage(0);
+    \Drupal::request()->query->set('page', 0);
     $entities[1]->name->value = $random_name = $this->randomMachineName();
     $entities[1]->save();
     $build = $this->assertViewsCacheTags($view, $tags_page_1, $do_assert_views_caches, $tags_page_1);
+    $this->assertViewsCacheTagsFromStaticRenderArray($view, $tags_page_1, $do_assert_views_caches);
     $this->assertTrue(strpos($build['#markup'], $random_name) !== FALSE);
   }
 
@@ -179,7 +193,7 @@ protected function assertCacheTagsForEntityBasedView($do_assert_views_caches) {
     // Empty result (no entities yet).
     $base_tags = $base_render_tags = ['config:views.view.entity_test_row', 'entity_test_list'];
     $this->assertViewsCacheTags($view, $base_tags, $do_assert_views_caches, $base_tags);
-
+    $this->assertViewsCacheTagsFromStaticRenderArray($view, $base_tags, $do_assert_views_caches);
 
     // Non-empty result (1 entity).
     $entities[] = $entity = EntityTest::create();
@@ -188,6 +202,7 @@ protected function assertCacheTagsForEntityBasedView($do_assert_views_caches) {
     $result_tags_with_entity = Cache::mergeTags($base_tags, $entities[0]->getCacheTags());
     $render_tags_with_entity = Cache::mergeTags($base_render_tags, $entities[0]->getCacheTags(), ['entity_test_view']);
     $this->assertViewsCacheTags($view, $result_tags_with_entity, $do_assert_views_caches, $render_tags_with_entity);
+    $this->assertViewsCacheTagsFromStaticRenderArray($view, $render_tags_with_entity, $do_assert_views_caches);
 
 
     // Paged result (more entities than the items-per-page limit).
@@ -200,6 +215,7 @@ protected function assertCacheTagsForEntityBasedView($do_assert_views_caches) {
     $result_tags_page_1 = Cache::mergeTags($base_tags, $new_entities_cache_tags);
     $render_tags_page_1 = Cache::mergeTags($base_render_tags, $new_entities_cache_tags, ['entity_test_view']);
     $this->assertViewsCacheTags($view, $result_tags_page_1, $do_assert_views_caches, $render_tags_page_1);
+    $this->assertViewsCacheTagsFromStaticRenderArray($view, $render_tags_page_1, $do_assert_views_caches);
   }
 
   /**
@@ -208,11 +224,11 @@ protected function assertCacheTagsForEntityBasedView($do_assert_views_caches) {
   public function testBuildRenderableWithCacheContexts() {
     $view = View::load('test_view');
     $display =& $view->getDisplay('default');
-    $display['cache_metadata']['contexts'] = ['beatles'];
+    $display['cache_metadata']['contexts'] = ['views_test_cache_context'];
     $executable = $view->getExecutable();
 
     $build = $executable->buildRenderable();
-    $this->assertEqual(['beatles'], $build['#cache']['contexts']);
+    $this->assertEqual(['views_test_cache_context'], $build['#cache']['contexts']);
   }
 
   /**
@@ -222,7 +238,7 @@ public function testViewAddCacheMetadata() {
     $view = View::load('test_display');
     $view->save();
 
-    $this->assertEqual(['languages:' . LanguageInterface::TYPE_CONTENT, 'languages:' . LanguageInterface::TYPE_INTERFACE, 'url.query_args.pagers:0', 'user.node_grants:view'], $view->getDisplay('default')['cache_metadata']['contexts']);
+    $this->assertEqual(['languages:' . LanguageInterface::TYPE_CONTENT, 'languages:' . LanguageInterface::TYPE_INTERFACE, 'url.query_args.pagers:0', 'user.node_grants:view', 'user.permissions'], $view->getDisplay('default')['cache_metadata']['contexts']);
   }
 
 }
diff --git a/core/modules/views/src/Tests/ViewAjaxTest.php b/core/modules/views/src/Tests/ViewAjaxTest.php
index bb4c76c..35b5518 100644
--- a/core/modules/views/src/Tests/ViewAjaxTest.php
+++ b/core/modules/views/src/Tests/ViewAjaxTest.php
@@ -54,11 +54,14 @@ public function testAjaxView() {
     $response = $this->drupalPost('views/ajax', 'application/vnd.drupal-ajax', $post);
     $data = Json::decode($response);
 
+    $this->assertTrue(isset($data[0]['settings']['views']['ajaxViews']));
+    $this->assertEqual($data[1]['command'], 'add_css');
+
     // Ensure that the view insert command is part of the result.
-    $this->assertEqual($data[1]['command'], 'insert');
-    $this->assertTrue(strpos($data[1]['selector'], '.js-view-dom-id-') === 0);
+    $this->assertEqual($data[2]['command'], 'insert');
+    $this->assertTrue(strpos($data[2]['selector'], '.js-view-dom-id-') === 0);
 
-    $this->setRawContent($data[1]['data']);
+    $this->setRawContent($data[2]['data']);
     $result = $this->xpath('//div[contains(@class, "views-row")]');
     $this->assertEqual(count($result), 2, 'Ensure that two items are rendered in the HTML.');
   }
diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php
index d5cd460..1c17ab5 100644
--- a/core/modules/views/src/ViewExecutable.php
+++ b/core/modules/views/src/ViewExecutable.php
@@ -406,7 +406,8 @@ class ViewExecutable implements \Serializable {
     '#attached' => [
       'library' => [],
       'drupalSettings' => [],
-    ]
+    ],
+    '#cache' => [],
   ];
 
   /**
@@ -490,21 +491,31 @@ public function setArguments(array $args) {
   }
 
   /**
+   * Expands the list of used cache contexts for the view.
+   *
+   * @param string $cache_context
+   *   The additional cache context.
+   *
+   * @retun $this
+   */
+  public function addCacheContext($cache_context) {
+    $this->element['#cache']['contexts'][] = $cache_context;
+  }
+
+  /**
    * Change/Set the current page for the pager.
    *
    * @param int $page
    *   The current page.
-   * @param bool $keep_cacheability
-   *   (optional) Keep the cacheability. By default we mark the view as not
-   *   cacheable. The reason for this parameter is that we do not know what the
-   *   passed in value varies by. For example, it could be per role. Defaults to
-   *   FALSE.
    */
-  public function setCurrentPage($page, $keep_cacheability = FALSE) {
+  public function setCurrentPage($page) {
     $this->current_page = $page;
 
-    if (!$keep_cacheability) {
-      $this->element['#cache']['max-age'] = 0;
+    // Calls like ::unserialize() might call this method without a proper $page.
+    // Also check whether the element is pre rendered. At that point, the cache
+    // keys cannot longer be manipulated.
+    if ($page !== NULL && empty($this->element['#pre_rendered'])) {
+      $this->element['#cache']['keys'][] = 'page:' . $page;
     }
 
     // If the pager is already initialized, pass it through to the pager.
@@ -546,23 +557,15 @@ public function getItemsPerPage() {
    *
    * @param int $items_per_page
    *   The items per page.
-   * @param bool $keep_cacheability
-   *   (optional) Keep the cacheability. By default we mark the view as not
-   *   cacheable. The reason for this parameter is that we do not know what the
-   *   passed in value varies by. For example, it could be per role. Defaults to
-   *   FALSE.
    */
-  public function setItemsPerPage($items_per_page, $keep_cacheability = FALSE) {
+  public function setItemsPerPage($items_per_page) {
+    $this->element['#cache']['keys'][] = 'items_per_page:' . $items_per_page;
     $this->items_per_page = $items_per_page;
 
     // If the pager is already initialized, pass it through to the pager.
     if (!empty($this->pager)) {
       $this->pager->setItemsPerPage($items_per_page);
     }
-
-    if (!$keep_cacheability) {
-      $this->element['#cache']['max-age'] = 0;
-    }
   }
 
   /**
@@ -584,23 +587,15 @@ public function getOffset() {
    *
    * @param int $offset
    *   The pager offset.
-   * @param bool $keep_cacheability
-   *   (optional) Keep the cacheability. By default we mark the view as not
-   *   cacheable. The reason for this parameter is that we do not know what the
-   *   passed in value varies by. For example, it could be per role. Defaults to
-   *   FALSE.
    */
-  public function setOffset($offset, $keep_cacheability = FALSE) {
+  public function setOffset($offset) {
+    $this->element['#cache']['keys'][] = 'offset:' . $offset;
     $this->offset = $offset;
 
     // If the pager is already initialized, pass it through to the pager.
     if (!empty($this->pager)) {
       $this->pager->setOffset($offset);
     }
-
-    if (!$keep_cacheability) {
-      $this->element['#cache']['max-age'] = 0;
-    }
   }
 
   /**
@@ -1379,72 +1374,60 @@ public function render($display_id = NULL) {
       $cache = FALSE;
     }
     else {
+      /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
       $cache = $this->display_handler->getPlugin('cache');
     }
 
-    /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
-    if ($cache && $cache->cacheGet('output')) {
+    // Run preRender for the pager as it might change the result.
+    if (!empty($this->pager)) {
+      $this->pager->preRender($this->result);
     }
-    else {
-      if ($cache) {
-        $cache->cacheStart();
-      }
 
-      // Run preRender for the pager as it might change the result.
-      if (!empty($this->pager)) {
-        $this->pager->preRender($this->result);
-      }
-
-      // Initialize the style plugin.
-      $this->initStyle();
+    // Initialize the style plugin.
+    $this->initStyle();
 
-      if (!isset($this->response)) {
-        // Set the response so other parts can alter it.
-        $this->response = new Response('', 200);
-      }
-
-      // Give field handlers the opportunity to perform additional queries
-      // using the entire resultset prior to rendering.
-      if ($this->style_plugin->usesFields()) {
-        foreach ($this->field as $id => $handler) {
-          if (!empty($this->field[$id])) {
-            $this->field[$id]->preRender($this->result);
-          }
-        }
-      }
-
-      $this->style_plugin->preRender($this->result);
+    if (!isset($this->response)) {
+      // Set the response so other parts can alter it.
+      $this->response = new Response('', 200);
+    }
 
-      // Let each area handler have access to the result set.
-      $areas = array('header', 'footer');
-      // Only call preRender() on the empty handlers if the result is empty.
-      if (empty($this->result)) {
-        $areas[] = 'empty';
-      }
-      foreach ($areas as $area) {
-        foreach ($this->{$area} as $handler) {
-          $handler->preRender($this->result);
+    // Give field handlers the opportunity to perform additional queries
+    // using the entire resultset prior to rendering.
+    if ($this->style_plugin->usesFields()) {
+      foreach ($this->field as $id => $handler) {
+        if (!empty($this->field[$id])) {
+          $this->field[$id]->preRender($this->result);
         }
       }
+    }
 
-      // Let modules modify the view just prior to rendering it.
-      $module_handler->invokeAll('views_pre_render', array($this));
+    $this->style_plugin->preRender($this->result);
 
-      // Let the themes play too, because pre render is a very themey thing.
-      foreach ($themes as $theme_name) {
-        $function = $theme_name . '_views_pre_render';
-        if (function_exists($function)) {
-          $function($this);
-        }
+    // Let each area handler have access to the result set.
+    $areas = array('header', 'footer');
+    // Only call preRender() on the empty handlers if the result is empty.
+    if (empty($this->result)) {
+      $areas[] = 'empty';
+    }
+    foreach ($areas as $area) {
+      foreach ($this->{$area} as $handler) {
+        $handler->preRender($this->result);
       }
+    }
 
-      $this->display_handler->output = $this->display_handler->render();
+    // Let modules modify the view just prior to rendering it.
+    $module_handler->invokeAll('views_pre_render', array($this));
 
-      if ($cache) {
-        $cache->cacheSet('output');
+    // Let the themes play too, because pre render is a very themey thing.
+    foreach ($themes as $theme_name) {
+      $function = $theme_name . '_views_pre_render';
+      if (function_exists($function)) {
+        $function($this);
       }
     }
 
+    $this->display_handler->output = $this->display_handler->render();
+
     $exposed_form->postRender($this->display_handler->output);
 
     if ($cache) {
@@ -1492,12 +1475,14 @@ public function getCacheTags() {
    *   The display ID.
    * @param array $args
    *   An array of arguments passed along to the view.
+   * @param bool $cache
+   *   (optional) Should the result be render cached.
    *
    * @return array|null
    *   A renderable array with #type 'view' or NULL if the display ID was
    *   invalid.
    */
-  public function buildRenderable($display_id = NULL, $args = array()) {
+  public function buildRenderable($display_id = NULL, $args = array(), $cache = TRUE) {
     // @todo Extract that into a generic method.
     if (empty($this->current_display) || $this->current_display != $this->chooseDisplay($display_id)) {
       if (!$this->setDisplay($display_id)) {
@@ -1505,7 +1490,7 @@ public function buildRenderable($display_id = NULL, $args = array()) {
       }
     }
 
-    return $this->display_handler->buildRenderable($args);
+    return $this->display_handler->buildRenderable($args, $cache);
   }
 
   /**
@@ -2399,7 +2384,7 @@ public function unserialize($serialized) {
 
     $this->setDisplay($current_display);
     $this->setArguments($args);
-    $this->setCurrentPage($current_page, TRUE);
+    $this->setCurrentPage($current_page);
     $this->setExposedInput($exposed_input);
     $this->exposed_data = $exposed_data;
     $this->exposed_raw_input = $exposed_raw_input;
diff --git a/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php b/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php
index a7282c7..50b4490 100644
--- a/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/Block/ViewsBlockTest.php
@@ -58,6 +58,13 @@ class ViewsBlockTest extends UnitTestCase {
   protected $account;
 
   /**
+   * The mocked display handler.
+   *
+   * @var \Drupal\views\Plugin\views\display\Block|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $displayHandler;
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
@@ -78,6 +85,9 @@ protected function setUp() {
       ->method('setDisplay')
       ->with('block_1')
       ->will($this->returnValue(TRUE));
+    $this->executable->expects($this->any())
+      ->method('getShowAdminLinks')
+      ->willReturn(FALSE);
 
     $this->executable->display_handler = $this->getMockBuilder('Drupal\views\Plugin\views\display\Block')
       ->disableOriginalConstructor()
@@ -87,6 +97,10 @@ protected function setUp() {
     $this->view = $this->getMockBuilder('Drupal\views\Entity\View')
       ->disableOriginalConstructor()
       ->getMock();
+    $this->view->expects($this->any())
+      ->method('id')
+      ->willReturn('test_view');
+    $this->executable->storage = $this->view;
 
     $this->executableFactory = $this->getMockBuilder('Drupal\views\ViewExecutableFactory')
       ->disableOriginalConstructor()
@@ -96,6 +110,19 @@ protected function setUp() {
       ->with($this->view)
       ->will($this->returnValue($this->executable));
 
+    $this->displayHandler = $this->getMockBuilder('Drupal\views\Plugin\views\display\Block')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->displayHandler->expects($this->any())
+      ->method('blockSettings')
+      ->willReturn([]);
+
+    $this->displayHandler->expects($this->any())
+      ->method('getPluginId')
+      ->willReturn('block');
+    $this->executable->display_handler = $this->displayHandler;
+
     $this->storage = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityStorage')
       ->disableOriginalConstructor()
       ->getMock();
@@ -114,7 +141,7 @@ protected function setUp() {
    */
   public function testBuild() {
     $output = $this->randomMachineName(100);
-    $build = array('#markup' => $output);
+    $build = array('#markup' => $output, '#view_id' => 'test_view', '#view_display_plugin_class' => '\Drupal\views\Plugin\views\display\Block', '#view_display_show_admin_links' => FALSE, '#view_display_plugin_id' => 'block');
     $this->executable->expects($this->once())
       ->method('buildRenderable')
       ->with('block_1', [])
diff --git a/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php
index 0cc643a..b0ebe13 100644
--- a/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php
@@ -28,7 +28,7 @@ class PathPluginBaseTest extends UnitTestCase {
   /**
    * The tested path plugin base.
    *
-   * @var \Drupal\views\Plugin\views\display\PathPluginBase
+   * @var \Drupal\views\Plugin\views\display\PathPluginBase|\PHPUnit_Framework_MockObject_MockObject
    */
   protected $pathPlugin;
 
@@ -107,6 +107,90 @@ public function testCollectRoutes() {
     $this->assertTrue($route instanceof Route);
     $this->assertEquals('test_id', $route->getDefault('view_id'));
     $this->assertEquals('page_1', $route->getDefault('display_id'));
+    $this->assertSame(FALSE, $route->getOption('returns_response'));
+  }
+
+  /**
+   * Tests the collectRoutes method with a display returning a response.
+   *
+   * @see \Drupal\views\Plugin\views\display\PathPluginBase::collectRoutes()
+   */
+  public function testCollectRoutesWithDisplayReturnResponse() {
+    list($view) = $this->setupViewExecutableAccessPlugin();
+
+    $display = array();
+    $display['display_plugin'] = 'page';
+    $display['id'] = 'page_1';
+    $display['display_options'] = array(
+      'path' => 'test_route',
+    );
+    $this->pathPlugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase')
+      ->setConstructorArgs(array(array(), 'path_base', array('returns_response' => TRUE), $this->routeProvider, $this->state))
+      ->setMethods(NULL)
+      ->getMock();
+    $this->pathPlugin->initDisplay($view, $display);
+
+    $collection = new RouteCollection();
+    $this->pathPlugin->collectRoutes($collection);
+    $route = $collection->get('view.test_id.page_1');
+    $this->assertSame(TRUE, $route->getOption('returns_response'));
+  }
+
+  /**
+   * Tests the collectRoutes method with arguments.
+   *
+   * @see \Drupal\views\Plugin\views\display\PathPluginBase::collectRoutes()
+   */
+  public function testCollectRoutesWithArguments() {
+    list($view) = $this->setupViewExecutableAccessPlugin();
+
+    $display = array();
+    $display['display_plugin'] = 'page';
+    $display['id'] = 'page_1';
+    $display['display_options'] = array(
+      'path' => 'test_route/%/example',
+    );
+    $this->pathPlugin->initDisplay($view, $display);
+
+    $collection = new RouteCollection();
+    $result = $this->pathPlugin->collectRoutes($collection);
+    $this->assertEquals(array('test_id.page_1' => 'view.test_id.page_1'), $result);
+
+    $route = $collection->get('view.test_id.page_1');
+    $this->assertTrue($route instanceof Route);
+    $this->assertEquals('test_id', $route->getDefault('view_id'));
+    $this->assertEquals('page_1', $route->getDefault('display_id'));
+    $this->assertEquals(array('arg_0' => 'arg_0'), $route->getOption('_view_argument_map'));
+  }
+
+  /**
+   * Tests the collectRoutes method with arguments not specified in the path.
+   *
+   * @see \Drupal\views\Plugin\views\display\PathPluginBase::collectRoutes()
+   */
+  public function testCollectRoutesWithArgumentsNotSpecifiedInPath() {
+    list($view) = $this->setupViewExecutableAccessPlugin();
+
+    $display = array();
+    $display['display_plugin'] = 'page';
+    $display['id'] = 'page_1';
+    $display['display_options'] = array(
+      'path' => 'test_with_arguments',
+    );
+    $display['display_options']['arguments'] = array(
+      'test_id' => array(),
+    );
+    $this->pathPlugin->initDisplay($view, $display);
+
+    $collection = new RouteCollection();
+    $result = $this->pathPlugin->collectRoutes($collection);
+    $this->assertEquals(array('test_id.page_1' => 'view.test_id.page_1'), $result);
+
+    $route = $collection->get('view.test_id.page_1');
+    $this->assertTrue($route instanceof Route);
+    $this->assertEquals('test_id', $route->getDefault('view_id'));
+    $this->assertEquals('page_1', $route->getDefault('display_id'));
+    $this->assertEquals(array('arg_0' => 'arg_0'), $route->getOption('_view_argument_map'));
   }
 
   /**
diff --git a/core/modules/views/tests/src/Unit/Routing/ViewPageControllerTest.php b/core/modules/views/tests/src/Unit/Routing/ViewPageControllerTest.php
index 3197aae..4bf01b1 100644
--- a/core/modules/views/tests/src/Unit/Routing/ViewPageControllerTest.php
+++ b/core/modules/views/tests/src/Unit/Routing/ViewPageControllerTest.php
@@ -43,6 +43,18 @@ class ViewPageControllerTest extends UnitTestCase {
    */
   protected $executableFactory;
 
+  /**
+   * A render array expected for every page controller render array result.
+   *
+   * @var array
+   */
+  protected $defaultRenderArray = [
+    '#cache_properties' => ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'],
+    '#view_id' => 'test_page_view',
+    '#view_display_plugin_id' => NULL,
+    '#view_display_show_admin_links' => NULL,
+  ];
+
   protected function setUp() {
     $this->storage = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityStorage')
       ->disableOriginalConstructor()
@@ -58,52 +70,30 @@ protected function setUp() {
    * Tests the page controller.
    */
   public function testPageController() {
-    $view = $this->getMock('Drupal\views\ViewEntityInterface');
-
-    $this->storage->expects($this->once())
-      ->method('load')
-      ->with('test_page_view')
-      ->will($this->returnValue($view));
-
-    $executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $executable->expects($this->once())
-      ->method('setDisplay')
-      ->with('default');
-    $executable->expects($this->once())
-      ->method('initHandlers');
-
-    $views_display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $views_display->expects($this->any())
-      ->method('getDefinition')
-      ->willReturn([]);
-    $executable->display_handler = $views_display;
+    $this->storage->expects($this->never())
+      ->method('load');
 
     $build = [
       '#type' => 'view',
       '#name' => 'test_page_view',
-      '#display_id' => 'default'
-    ];
-    $executable->expects($this->once())
-      ->method('buildRenderable')
-      ->with('default', [])
-      ->will($this->returnValue($build));
-
-    $this->executableFactory->expects($this->any())
-      ->method('get')
-      ->with($view)
-      ->will($this->returnValue($executable));
+      '#display_id' => 'default',
+      '#embed' => FALSE,
+      '#arguments' => [],
+      '#cache' => [
+        'keys' => ['views', 'display', 'test_page_view', 'default'],
+      ],
+    ] + $this->defaultRenderArray;
 
     $request = new Request();
     $request->attributes->set('view_id', 'test_page_view');
     $request->attributes->set('display_id', 'default');
-    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test', ['view_id' => 'test_page_view', 'display_id' => 'default']));
+    $options = [
+      '_view_display_plugin_class' => '\Drupal\views\Plugin\views\display\Page',
+    ];
+    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test', ['view_id' => 'test_page_view', 'display_id' => 'default'], [], $options));
     $route_match = RouteMatch::createFromRequest($request);
 
-    $output = $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $request, $route_match);
+    $output = $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $route_match);
     $this->assertInternalType('array', $output);
     $this->assertEquals($build, $output);
   }
@@ -112,54 +102,35 @@ public function testPageController() {
    * Tests the page controller with arguments on a non overridden page view.
    */
   public function testHandleWithArgumentsWithoutOverridden() {
-    $view = $this->getMock('Drupal\views\ViewEntityInterface');
-
-    $this->storage->expects($this->once())
-      ->method('load')
-      ->with('test_page_view')
-      ->will($this->returnValue($view));
-
-    $executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $executable->expects($this->once())
-      ->method('setDisplay')
-      ->with('page_1');
-    $executable->expects($this->once())
-      ->method('initHandlers');
-
-    $views_display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $views_display->expects($this->any())
-      ->method('getDefinition')
-      ->willReturn([]);
-    $executable->display_handler = $views_display;
-
-    // Manually setup a argument handler.
-    $argument = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $executable->argument['test_id'] = $argument;
-
-    $executable->expects($this->once())
-      ->method('buildRenderable')
-      ->with('page_1', array('test-argument'));
-
-    $this->executableFactory->expects($this->any())
-      ->method('get')
-      ->with($view)
-      ->will($this->returnValue($executable));
+    $this->storage->expects($this->never())
+      ->method('load');
 
     $request = new Request();
     $request->attributes->set('view_id', 'test_page_view');
     $request->attributes->set('display_id', 'page_1');
     // Add the argument to the request.
     $request->attributes->set('arg_0', 'test-argument');
-    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test/{arg_0}', ['view_id' => 'test_page_view', 'display_id' => 'default']));
+    $options = [
+      '_view_argument_map' => ['arg_0' => 'arg_0'],
+      '_view_display_plugin_class' => '\Drupal\views\Plugin\views\display\Page',
+    ];
+    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test/{arg_0}', ['view_id' => 'test_page_view', 'display_id' => 'default'], [], $options));
     $route_match = RouteMatch::createFromRequest($request);
 
-    $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $request, $route_match);
+    $result = $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $route_match);
+
+    $build = [
+      '#type' => 'view',
+      '#name' => 'test_page_view',
+      '#display_id' => 'page_1',
+      '#embed' => FALSE,
+      '#arguments' => ['test-argument'],
+      '#cache' => [
+        'keys' => ['views', 'display', 'test_page_view', 'page_1'],
+      ],
+    ] + $this->defaultRenderArray;
+
+    $this->assertEquals($build, $result);
   }
 
   /**
@@ -168,56 +139,37 @@ public function testHandleWithArgumentsWithoutOverridden() {
    * Note: This test does not care about upcasting for now.
    */
   public function testHandleWithArgumentsOnOverriddenRoute() {
-    $view = $this->getMock('Drupal\views\ViewEntityInterface');
-
-    $this->storage->expects($this->once())
-      ->method('load')
-      ->with('test_page_view')
-      ->will($this->returnValue($view));
-
-    $executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $executable->expects($this->once())
-      ->method('setDisplay')
-      ->with('page_1');
-    $executable->expects($this->once())
-      ->method('initHandlers');
-
-    $views_display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $views_display->expects($this->any())
-      ->method('getDefinition')
-      ->willReturn([]);
-    $executable->display_handler = $views_display;
-
-    // Manually setup a argument handler.
-    $argument = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $executable->argument['test_id'] = $argument;
-
-    $executable->expects($this->once())
-      ->method('buildRenderable')
-      ->with('page_1', array('test-argument'));
-
-    $this->executableFactory->expects($this->any())
-      ->method('get')
-      ->with($view)
-      ->will($this->returnValue($executable));
+    $this->storage->expects($this->never())
+      ->method('load');
 
     $request = new Request();
     $request->attributes->set('view_id', 'test_page_view');
     $request->attributes->set('display_id', 'page_1');
     // Add the argument to the request.
     $request->attributes->set('parameter', 'test-argument');
-    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test/{parameter}', ['view_id' => 'test_page_view', 'display_id' => 'default'], [], ['_view_argument_map' => [
-      'arg_0' => 'parameter',
-    ]]));
+    $options = [
+      '_view_argument_map' => [
+        'arg_0' => 'parameter',
+      ],
+      '_view_display_plugin_class' => '\Drupal\views\Plugin\views\display\Page',
+    ];
+    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test/{parameter}', ['view_id' => 'test_page_view', 'display_id' => 'default'], [], $options));
     $route_match = RouteMatch::createFromRequest($request);
 
-    $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $request, $route_match);
+    $result = $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $route_match);
+
+    $build = [
+      '#type' => 'view',
+      '#name' => 'test_page_view',
+      '#display_id' => 'page_1',
+      '#embed' => FALSE,
+      '#arguments' => ['test-argument'],
+      '#cache' => [
+        'keys' => ['views', 'display', 'test_page_view', 'page_1'],
+      ],
+      ] + $this->defaultRenderArray;
+
+    $this->assertEquals($build, $result);
   }
 
   /**
@@ -227,44 +179,8 @@ public function testHandleWithArgumentsOnOverriddenRoute() {
    * are pulled in.
    */
   public function testHandleWithArgumentsOnOverriddenRouteWithUpcasting() {
-    $view = $this->getMock('Drupal\views\ViewEntityInterface');
-
-    $this->storage->expects($this->once())
-      ->method('load')
-      ->with('test_page_view')
-      ->will($this->returnValue($view));
-
-    $executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $executable->expects($this->once())
-      ->method('setDisplay')
-      ->with('page_1');
-    $executable->expects($this->once())
-      ->method('initHandlers');
-
-    $views_display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DisplayPluginBase')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $views_display->expects($this->any())
-      ->method('getDefinition')
-      ->willReturn([]);
-    $executable->display_handler = $views_display;
-
-    // Manually setup a argument handler.
-    $argument = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $executable->argument['test_id'] = $argument;
-
-    $executable->expects($this->once())
-      ->method('buildRenderable')
-      ->with('page_1', array('example_id'));
-
-    $this->executableFactory->expects($this->any())
-      ->method('get')
-      ->with($view)
-      ->will($this->returnValue($executable));
+    $this->storage->expects($this->never())
+      ->method('load');
 
     $request = new Request();
     $request->attributes->set('view_id', 'test_page_view');
@@ -273,29 +189,29 @@ public function testHandleWithArgumentsOnOverriddenRouteWithUpcasting() {
     $request->attributes->set('test_entity', $this->getMock('Drupal\Core\Entity\EntityInterface'));
     $raw_variables = new ParameterBag(array('test_entity' => 'example_id'));
     $request->attributes->set('_raw_variables', $raw_variables);
-    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test/{test_entity}', ['view_id' => 'test_page_view', 'display_id' => 'default'], [], ['_view_argument_map' => [
-      'arg_0' => 'test_entity',
-    ]]));
+    $options = [
+      '_view_argument_map' => [
+        'arg_0' => 'test_entity',
+      ],
+      '_view_display_plugin_class' => '\Drupal\views\Plugin\views\display\Page',
+    ];
+    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test/{test_entity}', ['view_id' => 'test_page_view', 'display_id' => 'default'], [], $options));
     $route_match = RouteMatch::createFromRequest($request);
 
-    $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $request, $route_match);
-  }
-
-  /**
-   * Tests handle with a non existing view.
-   *
-   * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
-   */
-  public function testHandleWithNotExistingView() {
-    // Pass in a non existent view.
-    $random_view_id = $this->randomMachineName();
-
-    $request = new Request();
-    $request->attributes->set('view_id', $random_view_id);
-    $request->attributes->set('display_id', 'default');
-    $route_match = RouteMatch::createFromRequest($request);
+    $result = $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $route_match);
 
-    $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $request, $route_match);
+    $build = [
+      '#type' => 'view',
+      '#name' => 'test_page_view',
+      '#display_id' => 'page_1',
+      '#embed' => FALSE,
+      '#arguments' => ['example_id'],
+      '#cache' => [
+        'keys' => ['views', 'display', 'test_page_view', 'page_1'],
+      ],
+      ] + $this->defaultRenderArray;
+
+    $this->assertEquals($build, $result);
   }
 
 }
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index c19695d..639fc68 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -19,6 +19,7 @@
 use Drupal\Core\Url;
 use Drupal\views\Plugin\Derivative\ViewsLocalTask;
 use Drupal\Core\Template\AttributeArray;
+use Drupal\views\Plugin\views\display\Page;
 use Drupal\views\ViewExecutable;
 use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\views\Entity\View;
@@ -313,11 +314,14 @@ function views_element_info_alter(&$types) {
  * #pre_render callback to set contextual links for views using a Page display.
  */
 function views_page_display_pre_render(array $element) {
+  if (!\Drupal::moduleHandler()->moduleExists('contextual')) {
+    return $element;
+  }
   // 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 ($view = views_get_page_view()) {
-    views_add_contextual_links($element, 'page', $view, $view->current_display);
+  if ($render_array = Page::getPageRenderArray()) {
+    views_add_contextual_links($element, 'page', $render_array['#display_id'], $render_array);
   }
   return $element;
 }
@@ -410,15 +414,23 @@ function views_preprocess_html(&$variables) {
  * @see views_preprocess_page()
  * @see template_preprocess_views_view()
  */
-function views_add_contextual_links(&$render_element, $location, ViewExecutable $view, $display_id) {
+function views_add_contextual_links(&$render_element, $location, $display_id, array $view_element = NULL) {
+
+  if (!isset($view_element)) {
+    $view_element = $render_element;
+  }
+  $view_element['#cache_properties'] = ['view_id', 'view_display_show_admin_links', 'view_display_plugin_id'];
+  $view_id = $view_element['#view_id'];
+  $show_admin_links = $view_element['#view_display_show_admin_links'];
+  $display_plugin_id = $view_element['#view_display_plugin_id'];
+
   // Do not do anything if the view is configured to hide its administrative
   // links or if the Contextual Links module is not enabled.
-  if (\Drupal::moduleHandler()->moduleExists('contextual') && $view->getShowAdminLinks()) {
+  if (\Drupal::moduleHandler()->moduleExists('contextual') && $show_admin_links) {
     // Also do not do anything if the display plugin has not defined any
     // contextual links that are intended to be displayed in the requested
     // location.
-    $plugin_id = $view->displayHandlers->get($display_id)->getPluginId();
-    $plugin = Views::pluginManager('display')->getDefinition($plugin_id);
+    $plugin = Views::pluginManager('display')->getDefinition($display_plugin_id);
     // If contextual_links_locations are not set, provide a sane default. (To
     // avoid displaying any contextual links at all, a display plugin can still
     // set 'contextual_links_locations' to, e.g., {""}.)
@@ -441,17 +453,20 @@ function views_add_contextual_links(&$render_element, $location, ViewExecutable
         $args = array();
         $valid = TRUE;
         if (!empty($link['route_parameters_names'])) {
+          $view_storage = \Drupal::entityManager()
+            ->getStorage('view')
+            ->load($view_id);
           foreach ($link['route_parameters_names'] as $parameter_name => $property) {
             // If the plugin is trying to create an invalid contextual link
             // (for example, "path/to/{$view->storage->property}", where
             // $view->storage->{property} does not exist), we cannot construct
             // the link, so we skip it.
-            if (!property_exists($view->storage, $property)) {
+            if (!property_exists($view_storage, $property)) {
               $valid = FALSE;
               break;
             }
             else {
-              $args[$parameter_name] = $view->storage->get($property);
+              $args[$parameter_name] = $view_storage->get($property);
             }
           }
         }
@@ -463,13 +478,14 @@ function views_add_contextual_links(&$render_element, $location, ViewExecutable
             'route_parameters' => $args,
             'metadata' => array(
               'location' => $location,
-              'name' => $view->storage->id(),
+              'name' => $view_id,
               'display_id' => $display_id,
             ),
           );
           // If we're setting contextual links on a page, for a page view, for a
           // user that may use contextual links, attach Views' contextual links
           // JavaScript.
+          $render_element['#cache']['contexts'][] = 'user.permissions';
           if ($location === 'page' && $render_element['#type'] === 'page' && \Drupal::currentUser()->hasPermission('access contextual links')) {
             $render_element['#attached']['library'][] = 'views/views.contextual-links';
           }
@@ -522,32 +538,6 @@ function views_invalidate_cache() {
 }
 
 /**
- * Set the current 'page view' that is being displayed so that it is easy
- * for other modules or the theme to identify.
- */
-function &views_set_page_view($view = NULL) {
-  static $cache = NULL;
-  if (isset($view)) {
-    $cache = $view;
-  }
-
-  return $cache;
-}
-
-/**
- * Find out what, if any, page view is currently in use.
- *
- * Note that this returns a reference, so be careful! You can unintentionally
- * modify the $view object.
- *
- * @return \Drupal\views\ViewExecutable
- *   A fully formed, empty $view object.
- */
-function &views_get_page_view() {
-  return views_set_page_view();
-}
-
-/**
  * Set the current 'current view' that is being built/rendered so that it is
  * easy for other modules or items in drupal_eval to identify
  *
diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
index 236b6d0..98604a0 100644
--- a/core/modules/views/views.theme.inc
+++ b/core/modules/views/views.theme.inc
@@ -917,7 +917,12 @@ function template_preprocess_views_view_row_rss(&$variables) {
   $item = $variables['row'];
   $variables['title'] = $item->title;
   $variables['link'] = $item->link;
-  $variables['description'] = $item->description;
+
+  /** @var \Drupal\Core\Render\RendererInterface $renderer */
+  $renderer = \Drupal::service('renderer');
+  // We render the item description. It might contain entities, which attach rss
+  // elements via hook_entity_view, see comment_entity_view().
+  $variables['description'] = is_array($item->description) ? $renderer->render($item->description) : $item->description;
   $variables['item_elements'] = array();
   foreach ($item->elements as $element) {
     if (isset($element['attributes']) && is_array($element['attributes'])) {
