diff --git a/core/includes/common.inc b/core/includes/common.inc
index 79b222490cc09385366fe788f6d0e1a10f03ff58..0735dfa3bb23ba74b6f0a60997fd7513e0e662ed 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3158,7 +3158,7 @@ function drupal_pre_render_link($element) {
  * A typical example comes from node links, which are stored in a renderable
  * array similar to this:
  * @code
- * $node->content['links'] = array(
+ * $build['links'] = array(
  *   '#theme' => 'links__node',
  *   '#pre_render' => array('drupal_pre_render_links'),
  *   'comment' => array(
@@ -3193,7 +3193,7 @@ function drupal_pre_render_link($element) {
  * {{ content.links.comment }}
  * @endcode
  *
- * (where $node->content has been transformed into $content before handing
+ * (where a node's content has been transformed into $content before handing
  * control to the node.html.twig template).
  *
  * The pre_render function defined here allows the above flexibility, but also
@@ -3529,16 +3529,6 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
     return '';
   }
 
-  // Collect all #post_render_cache callbacks associated with this element when:
-  // - about to store this element in the render cache, or when;
-  // - about to apply #post_render_cache callbacks.
-  if (isset($elements['#cache']) || !$is_recursive_call) {
-    $post_render_cache = drupal_render_collect_post_render_cache($elements);
-    if ($post_render_cache) {
-      $elements['#post_render_cache'] = $post_render_cache;
-    }
-  }
-
   // Add any JavaScript state information associated with the element.
   if (!empty($elements['#states'])) {
     drupal_process_states($elements);
@@ -3642,6 +3632,15 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
   $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
   $elements['#markup'] = $prefix . $elements['#children'] . $suffix;
 
+  // Collect all #post_render_cache callbacks associated with this element when:
+  // - about to store this element in the render cache, or when;
+  // - about to apply #post_render_cache callbacks.
+  if (!$is_recursive_call || isset($elements['#cache'])) {
+    $post_render_cache = drupal_render_collect_post_render_cache($elements);
+    if ($post_render_cache) {
+      $elements['#post_render_cache'] = $post_render_cache;
+    }
+  }
   // Collect all cache tags. This allows the caller of drupal_render() to also
   // access the complete list of cache tags.
   if (!$is_recursive_call || isset($elements['#cache'])) {
@@ -3720,6 +3719,10 @@ function render(&$element) {
     return NULL;
   }
   if (is_array($element)) {
+    // Early return if this element was pre-rendered (no need to re-render).
+    if (isset($element['#printed']) && $element['#printed'] == TRUE && isset($element['#markup']) && strlen($element['#markup']) > 0) {
+      return $element['#markup'];
+    }
     show($element);
     return drupal_render($element, TRUE);
   }
@@ -3880,6 +3883,9 @@ function drupal_render_cache_set(&$markup, array $elements) {
  * @param string $token
  *   A unique token to uniquely identify the placeholder.
  *
+ * @return string
+ *   The generated placeholder HTML.
+ *
  * @see drupal_render_cache_get()
  */
 function drupal_render_cache_generate_placeholder($callback, array $context, $token) {
@@ -3892,37 +3898,10 @@ function drupal_render_cache_generate_placeholder($callback, array $context, $to
 }
 
 /**
- * Pre-render callback: Renders a render cache placeholder into #markup.
- *
- * @param $elements
- *   A structured array whose keys form the arguments to l():
- *   - #callback: The #post_render_cache callback that will replace the
- *     placeholder with its eventual markup.
- *   - #context: An array providing context for the #post_render_cache callback.
- *
- * @return
- *   The passed-in element containing a render cache placeholder in '#markup'
- *   and a callback with context, keyed by a generated unique token in
- *   '#post_render_cache'.
- *
- * @see drupal_render_cache_generate_placeholder()
+ * Generates a unique token for use in a #post_render_cache placeholder.
  */
-function drupal_pre_render_render_cache_placeholder($element) {
-  $callback = $element['#callback'];
-  if (!is_callable($callback)) {
-    throw new Exception(t('#callback must be a callable function.'));
-  }
-  $context = $element['#context'];
-  if (!is_array($context)) {
-    throw new Exception(t('#context must be an array.'));
-  }
-  $token = \Drupal\Component\Utility\Crypt::randomBytesBase64(55);
-
-  // Generate placeholder markup and store #post_render_cache callback.
-  $element['#markup'] = drupal_render_cache_generate_placeholder($callback, $context, $token);
-  $element['#post_render_cache'][$callback][$token] = $context;
-
-  return $element;
+function drupal_render_cache_generate_token() {
+  return \Drupal\Component\Utility\Crypt::randomBytesBase64(55);
 }
 
 /**
@@ -3948,42 +3927,11 @@ function _drupal_render_process_post_render_cache(array &$elements) {
     // and if keyed by a number, no token is passed, otherwise, the token string
     // is passed to the callback as well. This token is used to uniquely
     // identify the placeholder in the markup.
-    $modified_elements = $elements;
     foreach ($elements['#post_render_cache'] as $callback => $options) {
       foreach ($elements['#post_render_cache'][$callback] as $token => $context) {
-        // The advanced option, when setting #post_render_cache directly.
-        if (is_numeric($token)) {
-          $modified_elements = call_user_func_array($callback, array($modified_elements, $context));
-        }
-        // The simple option, when using the standard placeholders, and hence
-        // also when using #type => render_cache_placeholder.
-        else {
-          // Call #post_render_cache callback to generate the element that will
-          // fill in the placeholder.
-          $generated_element = call_user_func_array($callback, array($context));
-
-          // Update #attached based on the generated element.
-          if (isset($generated_element['#attached'])) {
-            if (!isset($modified_elements['#attached'])) {
-              $modified_elements['#attached'] = array();
-            }
-            $modified_elements['#attached'] = drupal_merge_attached($modified_elements['#attached'], drupal_render_collect_attached($generated_element, TRUE));
-          }
-
-          // Replace the placeholder with the rendered markup of the generated
-          // element.
-          $placeholder = drupal_render_cache_generate_placeholder($callback, $context, $token);
-          $modified_elements['#markup'] = str_replace($placeholder, drupal_render($generated_element), $modified_elements['#markup']);
-        }
+        $elements = call_user_func_array($callback, array($elements, $context));
       }
     }
-    // Only retain changes to the #markup and #attached properties, as would be
-    // the case when the render cache was actually being used.
-    $elements['#markup'] = $modified_elements['#markup'];
-    if (isset($modified_elements['#attached'])) {
-      $elements['#attached'] = $modified_elements['#attached'];
-    }
-
     // Make sure that any attachments added in #post_render_cache callbacks are
     // also executed.
     if (isset($elements['#attached'])) {
@@ -4040,13 +3988,6 @@ function drupal_render_collect_post_render_cache(array &$elements, array $callba
     }
   }
 
-  // If this is a render cache placeholder that hasn't been rendered yet, then
-  // render it now, because we must be able to collect its #post_render_cache
-  // callback.
-  if (!isset($elements['#post_render_cache']) && isset($elements['#type']) && $elements['#type'] === 'render_cache_placeholder') {
-    $elements = drupal_pre_render_render_cache_placeholder($elements);
-  }
-
   // Collect all #post_render_cache callbacks for this element.
   if (isset($elements['#post_render_cache'])) {
     $callbacks = NestedArray::mergeDeep($callbacks, $elements['#post_render_cache']);
diff --git a/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php b/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
index 8d86a6badc5bfded092236ae3d9c2b2f328acf32..69ecb78b2550e49442770df1c7b4d78ca50fd102 100644
--- a/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
+++ b/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
@@ -34,7 +34,7 @@ public function build(ContentEntityInterface $entity);
    *
    * This only includes the components handled by the Display object, but
    * excludes 'extra fields', that are typically rendered through specific,
-   * ad-hoc code in EntityViewBuilderInterface::buildContent() or in
+   * ad-hoc code in EntityViewBuilderInterface::buildComponents() or in
    * hook_entity_view() implementations.
    *
    * hook_entity_display_build_alter() is invoked on each entity, allowing 3rd
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index a0b8154c8cd63437d502bf7c9a4fc0a15f4120a6..9154dcd091f2b67b7ebfa3e2717745a548de7a0d 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -10,11 +10,13 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Field\FieldItemInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\TypedData\TranslatableInterface;
+use Drupal\Core\Render\Element;
 use Drupal\entity\Entity\EntityViewDisplay;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -89,13 +91,9 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
   /**
    * {@inheritdoc}
    */
-  public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
+  public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
     $entities_by_bundle = array();
     foreach ($entities as $id => $entity) {
-      // Remove previously built content, if exists.
-      $entity->content = array(
-        '#view_mode' => $view_mode,
-      );
       // Initialize the field item attributes for the fields being displayed.
       // The entity can include fields that are not displayed, and the display
       // can include components that are not fields, so we want to act on the
@@ -114,13 +112,13 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
     }
 
     // Invoke hook_entity_prepare_view().
-    \Drupal::moduleHandler()->invokeAll('entity_prepare_view', array($this->entityTypeId, $entities, $displays, $view_mode));
+    $this->moduleHandler()->invokeAll('entity_prepare_view', array($this->entityTypeId, $entities, $displays, $view_mode));
 
     // Let the displays build their render arrays.
     foreach ($entities_by_bundle as $bundle => $bundle_entities) {
-      $build = $displays[$bundle]->buildMultiple($bundle_entities);
+      $display_build = $displays[$bundle]->buildMultiple($bundle_entities);
       foreach ($bundle_entities as $id => $entity) {
-        $entity->content += $build[$id];
+        $build[$id] += $display_build[$id];
       }
     }
   }
@@ -133,26 +131,31 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
    * @param string $view_mode
    *   The view mode that should be used.
    * @param string $langcode
-   *   (optional) For which language the entity should be prepared, defaults to
+   *   For which language the entity should be prepared, defaults to
    *   the current content language.
    *
    * @return array
    */
   protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
-    $return = array(
+    // Allow modules to change the view mode.
+    $context = array('langcode' => $langcode);
+    $this->moduleHandler()->alter('entity_view_mode', $view_mode, $entity, $context);
+
+    $build = array(
       '#theme' => $this->entityTypeId,
       "#{$this->entityTypeId}" => $entity,
       '#view_mode' => $view_mode,
       '#langcode' => $langcode,
+      // Collect cache defaults for this entity.
       '#cache' => array(
-        'tags' =>  NestedArray::mergeDeep($this->getCacheTag(), $entity->getCacheTag()),
+        'tags' => NestedArray::mergeDeep($this->getCacheTag(), $entity->getCacheTag()),
       ),
     );
 
     // Cache the rendered output if permitted by the view mode and global entity
     // type configuration.
     if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
-      $return['#cache'] += array(
+      $build['#cache'] += array(
         'keys' => array(
           'entity_view',
           $this->entityTypeId,
@@ -165,11 +168,11 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
       );
 
       if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
-        $return['#cache']['keys'][] = $langcode;
+        $build['#cache']['keys'][] = $langcode;
       }
     }
 
-    return $return;
+    return $build;
   }
 
   /**
@@ -194,8 +197,16 @@ protected function alterBuild(array &$build, EntityInterface $entity, EntityView
    * {@inheritdoc}
    */
   public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
-    $buildList = $this->viewMultiple(array($entity), $view_mode, $langcode);
-    return $buildList[0];
+    $build_list = $this->viewMultiple(array($entity), $view_mode, $langcode);
+
+    // The default ::buildMultiple() #pre_render callback won't run, because we
+    // extract a child element of the default renderable array. Thus we must
+    // assign an alternative #pre_render callback that applies the necessary
+    // transformations and then still calls ::buildMultiple().
+    $build = $build_list[0];
+    $build['#pre_render'][] = array($this, 'build');
+
+    return $build;
   }
 
   /**
@@ -206,62 +217,122 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
       $langcode = $this->languageManager->getCurrentLanguage(Language::TYPE_CONTENT)->id;
     }
 
-    // Build the view modes and display objects.
-    $view_modes = array();
-    $context = array('langcode' => $langcode);
+    $build_list = array(
+      '#sorted' => TRUE,
+      '#pre_render' => array(array($this, 'buildMultiple')),
+      '#langcode' => $langcode,
+    );
+    $weight = 0;
     foreach ($entities as $key => $entity) {
-      $bundle = $entity->bundle();
-
       // Ensure that from now on we are dealing with the proper translation
       // object.
       $entity = $this->entityManager->getTranslationFromContext($entity, $langcode);
-      $entities[$key] = $entity;
 
-      // Allow modules to change the view mode.
-      $entity_view_mode = $view_mode;
-      $this->moduleHandler->alter('entity_view_mode', $entity_view_mode, $entity, $context);
-      // Store entities for rendering by view_mode.
-      $view_modes[$entity_view_mode][$entity->id()] = $entity;
-    }
+      // Set build defaults.
+      $build_list[$key] = $this->getBuildDefaults($entity, $view_mode, $langcode);
+      $entityType = $this->entityTypeId;
+      $this->moduleHandler()->alter(array($entityType . '_build_defaults', 'entity_build_defaults'), $build_list[$key], $entity, $view_mode, $langcode);
 
-    foreach ($view_modes as $mode => $view_mode_entities) {
-      $displays[$mode] = EntityViewDisplay::collectRenderDisplays($view_mode_entities, $mode);
-      $this->buildContent($view_mode_entities, $displays[$mode], $mode, $langcode);
+      $build_list[$key]['#weight'] = $weight++;
     }
 
+    return $build_list;
+  }
+
+  /**
+   * Builds an entity's view; augments entity defaults.
+   *
+   * This function is assigned as a #pre_render callback in ::view().
+   *
+   * It transforms the renderable array for a single entity to the same
+   * structure as if we were rendering multiple entities, and then calls the
+   * default ::buildMultiple() #pre_render callback.
+   *
+   * @param array $build
+   *   A renderable array containing build information and context for an entity
+   *   view.
+   *
+   * @return array
+   *   The updated renderable array.
+   *
+   * @see drupal_render()
+   */
+  public function build(array $build) {
+    $build_list = array(
+      '#langcode' => $build['#langcode'],
+    );
+    $build_list[] = $build;
+    $build_list = $this->buildMultiple($build_list);
+    return $build_list[0];
+  }
+
+  /**
+   * Builds multiple entities' views; augments entity defaults.
+   *
+   * This function is assigned as a #pre_render callback in ::viewMultiple().
+   *
+   * By delaying the building of an entity until the #pre_render processing in
+   * drupal_render(), the processing cost of assembling an entity's renderable
+   * array is saved on cache-hit requests.
+   *
+   * @param array $build_list
+   *   A renderable  array containing build information and context for an
+   *   entity view.
+   *
+   * @return array
+   *   The updated renderable array.
+   *
+   * @see drupal_render()
+   */
+  public function buildMultiple(array $build_list) {
+    // Build the view modes and display objects.
+    $view_modes = array();
+    $langcode = $build_list['#langcode'];
+    $entity_type_key = "#{$this->entityTypeId}";
     $view_hook = "{$this->entityTypeId}_view";
-    $build = array('#sorted' => TRUE);
-    $weight = 0;
-    foreach ($entities as $key => $entity) {
-      $entity_view_mode = isset($entity->content['#view_mode']) ? $entity->content['#view_mode'] : $view_mode;
-      $display = $displays[$entity_view_mode][$entity->bundle()];
-      \Drupal::moduleHandler()->invokeAll($view_hook, array($entity, $display, $entity_view_mode, $langcode));
-      \Drupal::moduleHandler()->invokeAll('entity_view', array($entity, $display, $entity_view_mode, $langcode));
-
-      $build[$key] = $entity->content;
-      // We don't need duplicate rendering info in $entity->content.
-      unset($entity->content);
-
-      $build[$key] += $this->getBuildDefaults($entity, $entity_view_mode, $langcode);
-      $this->alterBuild($build[$key], $entity, $display, $entity_view_mode, $langcode);
-
-      // Assign the weights configured in the display.
-      // @todo: Once https://drupal.org/node/1875974 provides the missing API,
-      //   only do it for 'extra fields', since other components have been taken
-      //   care of in EntityViewDisplay::buildMultiple().
-      foreach ($display->getComponents() as $name => $options) {
-        if (isset($build[$key][$name])) {
-          $build[$key][$name]['#weight'] = $options['weight'];
+
+    // Find the keys for the ContentEntities in the build; Store entities for
+    // rendering by view_mode.
+    $children = Element::children($build_list);
+    foreach ($children as $key) {
+      if (isset($build_list[$key][$entity_type_key])) {
+        $entity = $build_list[$key][$entity_type_key];
+        if ($entity instanceof ContentEntityInterface) {
+          $view_modes[$build_list[$key]['#view_mode']][$key] = $entity;
         }
       }
+    }
 
-      $build[$key]['#weight'] = $weight++;
+    // Build content for the displays represented by the entities.
+    foreach ($view_modes as $view_mode => $view_mode_entities) {
+      $displays = EntityViewDisplay::collectRenderDisplays($view_mode_entities, $view_mode);
+      $this->buildComponents($build_list, $view_mode_entities, $displays, $view_mode, $langcode);
+      foreach (array_keys($view_mode_entities) as $key) {
+        // Allow for alterations while building, before rendering.
+        $entity = $build_list[$key][$entity_type_key];
+        $display = $displays[$entity->bundle()];
+
+        $this->moduleHandler()->invokeAll($view_hook, array(&$build_list[$key], $entity, $display, $view_mode, $langcode));
+        $this->moduleHandler()->invokeAll('entity_view', array(&$build_list[$key], $entity, $display, $view_mode, $langcode));
+
+        $this->alterBuild($build_list[$key], $entity, $display, $view_mode, $langcode);
+
+        // Assign the weights configured in the display.
+        // @todo: Once https://drupal.org/node/1875974 provides the missing API,
+        //   only do it for 'extra fields', since other components have been
+        //   taken care of in EntityViewDisplay::buildMultiple().
+        foreach ($display->getComponents() as $name => $options) {
+          if (isset($build_list[$key][$name])) {
+            $build_list[$key]['#weight'] = $options['weight'];
+          }
+        }
 
-      // Allow modules to modify the render array.
-      $this->moduleHandler->alter(array($view_hook, 'entity_view'), $build[$key], $entity, $display);
+        // Allow modules to modify the render array.
+        $this->moduleHandler()->alter(array($view_hook, 'entity_view'), $build_list[$key], $entity, $display);
+      }
     }
 
-    return $build;
+    return $build_list;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
index aee9588d45ba838d457588f6a542e0b1163e54f7..ee1c193406a5a7b49dcee691933deaa014ea5b2b 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
@@ -16,8 +16,10 @@
 interface EntityViewBuilderInterface {
 
   /**
-   * Build the structured $content property on the entity.
+   * Builds the component fields and properties of a set of entities.
    *
+   * @param &$build
+   *   The renderable array representing the entity content.
    * @param \Drupal\Core\Entity\EntityInterface[] $entities
    *   The entities whose content is being built.
    * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface[] $displays
@@ -28,11 +30,8 @@ interface EntityViewBuilderInterface {
    * @param string $langcode
    *   (optional) For which language the entity should be build, defaults to
    *   the current content language.
-   *
-   * @return array
-   *   The content array.
    */
-  public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL);
+  public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL);
 
   /**
    * Returns the render array for the provided entity.
diff --git a/core/lib/Drupal/Core/Field/FormatterBase.php b/core/lib/Drupal/Core/Field/FormatterBase.php
index e1646ff2ce14457cff436fe906044df689c58a9e..eeb94a349e37c7f713af86b7fda885a90bfdae44 100644
--- a/core/lib/Drupal/Core/Field/FormatterBase.php
+++ b/core/lib/Drupal/Core/Field/FormatterBase.php
@@ -90,21 +90,8 @@ public function view(FieldItemListInterface $items) {
         '#object' => $entity,
         '#items' => $items,
         '#formatter' => $this->getPluginId(),
-        '#cache' => array('tags' => array())
       );
 
-      // Gather cache tags from reference fields.
-      foreach ($items as $item) {
-        if (isset($item->format)) {
-          $info['#cache']['tags']['filter_format'] = $item->format;
-        }
-
-        if (isset($item->entity)) {
-          $info['#cache']['tags'][$item->entity->getEntityTypeId()][] = $item->entity->id();
-          $info['#cache']['tags'][$item->entity->getEntityTypeId() . '_view'] = TRUE;
-        }
-      }
-
       $addition = array_merge($info, $elements);
     }
 
diff --git a/core/modules/block/block.api.php b/core/modules/block/block.api.php
index 065a1cd7d75c1f983c954d6087fa6ff9d1cc53e2..9210f82536378fd6cbda9fb4d90176d863e9a950 100644
--- a/core/modules/block/block.api.php
+++ b/core/modules/block/block.api.php
@@ -27,7 +27,7 @@
  * is hook_block_view_BASE_BLOCK_ID_alter(), which can be used to target a
  * specific block or set of similar blocks.
  *
- * @param array $build
+ * @param array &$build
  *   A renderable array of data, as returned from the build() implementation of
  *   the plugin that defined the block:
  *   - #title: The default localized title of the block.
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockBuildContentTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockBuildContentTest.php
deleted file mode 100644
index cee6ce8d998601b45cc6da022ef96a6271ee2a48..0000000000000000000000000000000000000000
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockBuildContentTest.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\custom_block\Tests\CustomBlockBuildContentTest.
- */
-
-namespace Drupal\custom_block\Tests;
-
-/**
- * Test to ensure that a block's content is always rebuilt.
- */
-class CustomBlockBuildContentTest extends CustomBlockTestBase {
-
-  /**
-   * Declares test information.
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Rebuild content',
-      'description' => 'Test the rebuilding of content for full view modes.',
-      'group' => 'Custom Block',
-    );
-  }
-
-  /**
-   * Ensures that content is rebuilt in calls to custom_block_build_content().
-   */
-  public function testCustomBlockRebuildContent() {
-    $block = $this->createCustomBlock();
-
-    // Set a property in the content array so we can test for its existence later on.
-    $block->content['test_content_property'] = array(
-      '#value' => $this->randomString(),
-    );
-    $content = entity_view_multiple(array($block), 'full');
-
-    // If the property doesn't exist it means the block->content was rebuilt.
-    $this->assertFalse(isset($content['test_content_property']), 'Custom block content was emptied prior to being built.');
-  }
-}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCacheTagsTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCacheTagsTest.php
index ccd51dd19cf6dd13a048a20f74a5094fb0fa91b2..cfd87a957c22dfa2c7d0939c5a09649acc5be3d0 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCacheTagsTest.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCacheTagsTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\custom_block\Tests;
 
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
 
 /**
@@ -34,11 +35,23 @@ protected function createEntity() {
     $custom_block = entity_create('custom_block', array(
       'info' => 'Llama',
       'type' => 'basic',
-      'body' => 'The name "llama" was adopted by European settlers from native Peruvians.',
+      'body' => array(
+        'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
+        'format' => 'plain_text',
+      ),
     ));
     $custom_block->save();
 
     return $custom_block;
   }
 
+  /**
+   * {@inheritdoc}
+   *
+   * Each comment must have a comment body, which always has a text format.
+   */
+  protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
+    return array('filter_format:plain_text');
+  }
+
 }
diff --git a/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.module b/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.module
index 8c530539e06cddccdca0c22989c113fdfc3c9fe4..65e9c56790704cbf579776d9cd6c8d50cc04cf69 100644
--- a/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.module
+++ b/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.module
@@ -13,9 +13,9 @@
 /**
  * Implements hook_custom_block_view().
  */
-function custom_block_test_custom_block_view(CustomBlock $custom_block, $view_mode) {
+function custom_block_test_custom_block_view(array &$build, CustomBlock $custom_block, $view_mode) {
   // Add extra content.
-  $custom_block->content['extra_content'] = array(
+  $build['extra_content'] = array(
     '#markup' => '<blink>Yowser</blink>',
   );
 }
diff --git a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php
index 7db7b70c5911cb3e011d9912922e2570bf6c4af8..9e3b1b56106562d209505c15049beb7b2da3421f 100644
--- a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php
+++ b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php
@@ -22,8 +22,7 @@ class BlockViewBuilder extends EntityViewBuilder {
   /**
    * {@inheritdoc}
    */
-  public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
-    return array();
+  public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
   }
 
   /**
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 8c5222158b0942cf8d571e057a2d5e08aa2f96bc..e5bb818f164943d2bfb243f6d5459bd8f232971b 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -247,11 +247,11 @@ function book_node_load($nodes) {
 /**
  * Implements hook_node_view().
  */
-function book_node_view(EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
+function book_node_view(array &$build, EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
   if ($view_mode == 'full') {
     if (!empty($node->book['bid']) && empty($node->in_preview)) {
       $book_navigation = array( '#theme' => 'book_navigation', '#book_link' => $node->book);
-      $node->content['book_navigation'] = array(
+      $build['book_navigation'] = array(
         '#markup' => drupal_render($book_navigation),
         '#weight' => 100,
         '#attached' => array(
diff --git a/core/modules/comment/comment.api.php b/core/modules/comment/comment.api.php
index 8507f134797b7225b4ea8cfd2c60ef50822f1d5f..9d77557f43c717349e17ff7f3120df4c0c6bdb4b 100644
--- a/core/modules/comment/comment.api.php
+++ b/core/modules/comment/comment.api.php
@@ -84,6 +84,8 @@ function hook_comment_load(Drupal\comment\Comment $comments) {
 /**
  * Act on a comment that is being assembled before rendering.
  *
+ * @param array &$build
+ *   A renderable array representing the comment content.
  * @param \Drupal\comment\Entity\Comment $comment $comment
  *   Passes in the comment the action is being performed on.
  * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
@@ -96,12 +98,12 @@ function hook_comment_load(Drupal\comment\Comment $comments) {
  *
  * @see hook_entity_view()
  */
-function hook_comment_view(\Drupal\comment\Entity\Comment $comment, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
+function hook_comment_view(array &$build, \Drupal\comment\Entity\Comment $comment, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
   // Only do the extra work if the component is configured to be displayed.
   // This assumes a 'mymodule_addition' extra field has been defined for the
   // node type in hook_entity_extra_field_info().
   if ($display->getComponent('mymodule_addition')) {
-    $comment->content['mymodule_addition'] = array(
+    $build['mymodule_addition'] = array(
       '#markup' => mymodule_addition($comment),
       '#theme' => 'mymodule_my_additional_field',
     );
@@ -120,7 +122,7 @@ function hook_comment_view(\Drupal\comment\Entity\Comment $comment, \Drupal\Core
  * callback. Alternatively, it could also implement hook_preprocess_HOOK() for
  * comment.html.twig. See drupal_render() documentation for details.
  *
- * @param $build
+ * @param array &$build
  *   A renderable array representing the comment.
  * @param \Drupal\comment\Entity\Comment $comment
  *   The comment being rendered.
@@ -131,7 +133,7 @@ function hook_comment_view(\Drupal\comment\Entity\Comment $comment, \Drupal\Core
  * @see comment_view()
  * @see hook_entity_view_alter()
  */
-function hook_comment_view_alter(&$build, \Drupal\comment\Entity\Comment $comment, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
+function hook_comment_view_alter(array &$build, \Drupal\comment\Entity\Comment $comment, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
   // Check for the existence of a field added by another module.
   if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
     // Change its weight.
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 41154443bae06d1fdca0276f45571a57ea1c7ad5..4d35ff58f4db5e4f08dad1e5758b631344a5faab 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -13,6 +13,7 @@
 use Drupal\comment\CommentInterface;
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\entity\Entity\EntityViewDisplay;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Url;
@@ -359,20 +360,22 @@ function comment_new_page_count($num_comments, $new_replies, EntityInterface $en
 }
 
 /**
- * Implements hook_entity_view_alter().
+ * Implements hook_entity_build_defaults_alter().
  */
-function comment_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
+function comment_entity_build_defaults_alter(array &$build, EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
+  // Get the corresponding display settings.
+  $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
   // Add the comment page number to the cache key if render caching is enabled.
   if (isset($build['#cache']) && isset($build['#cache']['keys']) && \Drupal::request()->query->has('page')) {
     foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
-      if (isset($build[$field_name]) && $definition->getType() === 'comment') {
-        $display_options = $display->getComponent($field_name);
+      if ($definition->getType() === 'comment' && ($display_options = $display->getComponent($field_name))) {
         $pager_id = $display_options['settings']['pager_id'];
         $page = pager_find_page($pager_id);
         $build['#cache']['keys'][] = $field_name . '-pager-' . $page;
       }
     }
   }
+  return $build;
 }
 
 /**
@@ -534,7 +537,7 @@ function comment_node_links_alter(array &$node_links, NodeInterface $node, array
 /**
  * Implements hook_node_view_alter().
  */
-function comment_node_view_alter(&$build, EntityInterface $node, EntityViewDisplayInterface $display) {
+function comment_node_view_alter(array &$build, EntityInterface $node, EntityViewDisplayInterface $display) {
   if (\Drupal::moduleHandler()->moduleExists('history')) {
     $build['#attributes']['data-history-node-id'] = $node->id();
   }
@@ -1242,16 +1245,13 @@ function comment_preview(CommentInterface $comment, array &$form_state) {
     // therefore the rendered parent entity. This results in an infinite loop of
     // parent entity output rendering the comment form and the comment form
     // rendering the parent entity. To prevent this infinite loop we temporarily
-    // set the value of the comment field on the rendered entity to hidden
+    // set the value of the comment field on a clone of the entity to hidden
     // before calling entity_view(). That way when the output of the commented
-    // entity is rendered, it excludes the comment field output. As objects are
-    // always addressed by reference we ensure changes are not lost by setting
-    // the value back to its original state after the call to entity_view().
+    // entity is rendered, it excludes the comment field output.
     $field_name = $comment->getFieldName();
-    $original_status = $entity->get($field_name)->status;
-    $entity->get($field_name)->status = CommentItemInterface::HIDDEN;
+    $entity = clone $entity;
+    $entity->$field_name->status = CommentItemInterface::HIDDEN;
     $build = entity_view($entity, 'full');
-    $entity->get($field_name)->status = $original_status;
   }
 
   $preview_build['comment_output_below'] = $build;
@@ -1282,7 +1282,7 @@ function comment_preprocess_block(&$variables) {
  *   A user account, for use with theme_username() or the user_picture template.
  */
 function comment_prepare_author(CommentInterface $comment) {
-  // The account has been pre-loaded by CommentViewBuilder::buildContent().
+  // The account has been pre-loaded by CommentViewBuilder::buildComponents().
   $account = $comment->getOwner();
   if (empty($account->uid->value)) {
     // @todo Avoid creating a new entity by just creating a new instance
diff --git a/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php b/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php
index da28f7bbc3d09ec8f1130b7b220df60260abefec..02e427ff696c1af1d5e8bab06615ae0b680e9f35 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php
@@ -14,6 +14,8 @@
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityViewBuilder;
+use Drupal\entity\Entity\EntityViewDisplay;
+use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\field\FieldInfo;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -78,7 +80,7 @@ public function __construct(EntityTypeInterface $entity_type, EntityManagerInter
   }
 
   /**
-   * Overrides Drupal\Core\Entity\EntityViewBuilder::buildContent().
+   * Overrides Drupal\Core\Entity\EntityViewBuilder::buildComponents().
    *
    * In addition to modifying the content key on entities, this implementation
    * will also set the comment entity key which all comments carry.
@@ -86,11 +88,10 @@ public function __construct(EntityTypeInterface $entity_type, EntityManagerInter
    * @throws \InvalidArgumentException
    *   Thrown when a comment is attached to an entity that no longer exists.
    */
-  public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
+  public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
     /** @var \Drupal\comment\CommentInterface[] $entities */
-    $return = array();
     if (empty($entities)) {
-      return $return;
+      return;
     }
 
     // Pre-load associated users into cache to leverage multiple loading.
@@ -100,7 +101,7 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
     }
     $this->entityManager->getStorage('user')->loadMultiple(array_unique($uids));
 
-    parent::buildContent($entities, $displays, $view_mode, $langcode);
+    parent::buildComponents($build, $entities, $displays, $view_mode, $langcode);
 
     // Load all the entities that have comments attached.
     $commented_entity_ids = array();
@@ -114,37 +115,43 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
       $commented_entities[$entity_type] = $this->entityManager->getStorage($entity_type)->loadMultiple($entity_ids);
     }
 
-    foreach ($entities as $entity) {
+    foreach ($entities as $id => $entity) {
       if (isset($commented_entities[$entity->getCommentedEntityTypeId()][$entity->getCommentedEntityId()])) {
         $commented_entity = $commented_entities[$entity->getCommentedEntityTypeId()][$entity->getCommentedEntityId()];
       }
       else {
         throw new \InvalidArgumentException(t('Invalid entity for comment.'));
       }
-      $entity->content['#entity'] = $entity;
-      $entity->content['#theme'] = 'comment__' . $entity->getFieldId() . '__' . $commented_entity->bundle();
-      $entity->content['links'] = array(
-        '#type' => 'render_cache_placeholder',
-        '#callback' => '\Drupal\comment\CommentViewBuilder::renderLinks',
-        '#context' => array(
-          'comment_entity_id' => $entity->id(),
-          'view_mode' => $view_mode,
-          'langcode' => $langcode,
-          'commented_entity_type' => $commented_entity->getEntityTypeId(),
-          'commented_entity_id' => $commented_entity->id(),
-          'in_preview' => !empty($entity->in_preview),
+      $build[$id]['#entity'] = $entity;
+      $build[$id]['#theme'] = 'comment__' . $entity->getFieldId() . '__' . $commented_entity->bundle();
+      $callback = '\Drupal\comment\CommentViewBuilder::renderLinks';
+      $context = array(
+        'comment_entity_id' => $entity->id(),
+        'view_mode' => $view_mode,
+        'langcode' => $langcode,
+        'commented_entity_type' => $commented_entity->getEntityTypeId(),
+        'commented_entity_id' => $commented_entity->id(),
+        'in_preview' => !empty($entity->in_preview),
+        'token' => drupal_render_cache_generate_token(),
+      );
+      $build[$id]['links'] = array(
+        '#post_render_cache' => array(
+          $callback => array(
+            $context,
+          ),
         ),
+        '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
       );
 
-      if (!isset($entity->content['#attached'])) {
-        $entity->content['#attached'] = array();
+      if (!isset($build[$id]['#attached'])) {
+        $build[$id]['#attached'] = array();
       }
-      $entity->content['#attached']['library'][] = 'comment/drupal.comment-by-viewer';
+      $build[$id]['#attached']['library'][] = 'comment/drupal.comment-by-viewer';
       if ($this->moduleHandler->moduleExists('history') &&  \Drupal::currentUser()->isAuthenticated()) {
-        $entity->content['#attached']['library'][] = 'comment/drupal.comment-new-indicator';
+        $build[$id]['#attached']['library'][] = 'comment/drupal.comment-new-indicator';
 
         // Embed the metadata for the comment "new" indicators on this node.
-        $entity->content['#post_render_cache']['history_attach_timestamp'] = array(
+        $build[$id]['#post_render_cache']['history_attach_timestamp'] = array(
           array('node_id' => $commented_entity->id()),
         );
       }
@@ -156,6 +163,8 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
    *
    * Renders the links on a comment.
    *
+   * @param array $element
+   *   The renderable array that contains the to be replaced placeholder.
    * @param array $context
    *   An array with the following keys:
    *   - comment_entity_id: a comment entity ID
@@ -168,7 +177,9 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
    * @return array
    *   A renderable array representing the comment links.
    */
-  public static function renderLinks(array $context) {
+  public static function renderLinks(array $element, array $context) {
+    $callback = '\Drupal\comment\CommentViewBuilder::renderLinks';
+    $placeholder = drupal_render_cache_generate_placeholder($callback, $context, $context['token']);
     $links = array(
       '#theme' => 'links__comment',
       '#pre_render' => array('drupal_pre_render_links'),
@@ -189,8 +200,10 @@ public static function renderLinks(array $context) {
       );
       \Drupal::moduleHandler()->alter('comment_links', $links, $entity, $hook_context);
     }
+    $markup = drupal_render($links);
+    $element['#markup'] = str_replace($placeholder, $markup, $element['#markup']);
 
-    return $links;
+    return $element;
   }
 
   /**
diff --git a/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php b/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php
index 782995066771c667d861477c42281b969321dabc..55b8b3bef883b9ad13c4af499fcfadceb7e0dc93 100644
--- a/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php
+++ b/core/modules/comment/lib/Drupal/comment/Controller/CommentController.php
@@ -251,11 +251,11 @@ public function getReplyForm(Request $request, $entity_type, $entity_id, $field_
       elseif ($entity->access('view', $account)) {
         // We make sure the field value isn't set so we don't end up with a
         // redirect loop.
+        $entity = clone $entity;
         $entity->{$field_name}->status = CommentItemInterface::HIDDEN;
         // Render array of the entity full view mode.
         $build['commented_entity'] = $this->entityManager()->getViewBuilder($entity->getEntityTypeId())->view($entity, 'full');
         unset($build['commented_entity']['#cache']);
-        $entity->{$field_name}->status = $status;
       }
     }
     else {
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php b/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
index 639cbb0c6502303216d928206432e9a2be089649..19dd00ff33750a8ea72b8fc4dbf20c2548602e44 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
@@ -145,6 +145,20 @@ public function viewElements(FieldItemListInterface $items) {
           if ($this->getSetting('pager_id')) {
             $build['pager']['#element'] = $this->getSetting('pager_id');
           }
+          // The viewElements() method of entity field formatters is run
+          // during the #pre_render phase of rendering an entity. A formatter
+          // builds the content of the field in preparation for theming.
+          // All entity cache tags must be available after the #pre_render phase.
+          // This field formatter is highly exceptional: it renders *another*
+          // entity and this referenced entity has its own #pre_render
+          // callbacks. In order collect the cache tags associated with the
+          // referenced entity it must be passed to drupal_render() so that its
+          // #pre_render callbacks are invoked and its full build array is
+          // assembled. Rendering the referenced entity in place here will allow
+          // its cache tags to be bubbled up and included with those of the
+          // main entity when cache tags are collected for a renderable array
+          // in drupal_render().
+          drupal_render($build, TRUE);
           $output['comments'] = $build;
         }
       }
@@ -162,14 +176,20 @@ public function viewElements(FieldItemListInterface $items) {
           // All other users need a user-specific form, which would break the
           // render cache: hence use a #post_render_cache callback.
           else {
+            $callback = '\Drupal\comment\Plugin\Field\FieldFormatter\CommentDefaultFormatter::renderForm';
+            $context = array(
+              'entity_type' => $entity->getEntityTypeId(),
+              'entity_id' => $entity->id(),
+              'field_name' => $field_name,
+              'token' => drupal_render_cache_generate_token(),
+            );
             $output['comment_form'] = array(
-              '#type' => 'render_cache_placeholder',
-              '#callback' => '\Drupal\comment\Plugin\Field\FieldFormatter\CommentDefaultFormatter::renderForm',
-              '#context' => array(
-                'entity_type' => $entity->getEntityTypeId(),
-                'entity_id' => $entity->id(),
-                'field_name' => $field_name,
+              '#post_render_cache' => array(
+                $callback => array(
+                  $context,
+                ),
               ),
+              '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
             );
           }
         }
@@ -190,6 +210,8 @@ public function viewElements(FieldItemListInterface $items) {
   /**
    * #post_render_cache callback; replaces placeholder with comment form.
    *
+   * @param array $element
+   *   The renderable array that contains the to be replaced placeholder.
    * @param array $context
    *   An array with the following keys:
    *   - entity_type: an entity type
@@ -199,9 +221,17 @@ public function viewElements(FieldItemListInterface $items) {
    * @return array
    *   A renderable array containing the comment form.
    */
-  public static function renderForm(array $context) {
+  public static function renderForm(array $element, array $context) {
+    $callback = '\Drupal\comment\Plugin\Field\FieldFormatter\CommentDefaultFormatter::renderForm';
+    $placeholder = drupal_render_cache_generate_placeholder($callback, $context, $context['token']);
     $entity = entity_load($context['entity_type'], $context['entity_id']);
-    return comment_add($entity, $context['field_name']);
+    $form = comment_add($entity, $context['field_name']);
+    // @todo: This only works as long as assets are still tracked in a global
+    //   static variable, see https://drupal.org/node/2238835
+    $markup = drupal_render($form, TRUE);
+    $element['#markup'] = str_replace($placeholder, $markup, $element['#markup']);
+
+    return $element;
   }
 
   /**
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentCacheTagsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentCacheTagsTest.php
index 07cac6e3be13e9ea211295735ba67973c923b3a8..d12c6bb7626291536b1fb92214609eb27e4643b6 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentCacheTagsTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentCacheTagsTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\comment\Tests;
 
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
 
 /**
@@ -57,10 +58,13 @@ protected function createEntity() {
     ));
     $entity_test->save();
 
-    // Create a "Llama" taxonomy term.
+    // Create a "Llama" comment.
     $comment = entity_create('comment', array(
       'subject' => 'Llama',
-      'comment_body' => 'The name "llama" was adopted by European settlers from native Peruvians.',
+      'comment_body' => array(
+        'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
+        'format' => 'plain_text',
+      ),
       'entity_id' => $entity_test->id(),
       'entity_type' => 'entity_test',
       'field_name' => 'comment',
@@ -71,4 +75,13 @@ protected function createEntity() {
     return $comment;
   }
 
+  /**
+   * {@inheritdoc}
+   *
+   * Each comment must have a comment body, which always has a text format.
+   */
+  protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
+    return array('filter_format:plain_text');
+  }
+
 }
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentContentRebuildTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentContentRebuildTest.php
deleted file mode 100644
index c467a09b6920466f298ccd3bc9d4e4c20b0e8bb4..0000000000000000000000000000000000000000
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentContentRebuildTest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\comment\Tests\CommentContentRebuildTest.
- */
-
-namespace Drupal\comment\Tests;
-
-/**
- * Tests comment content rebuilding.
- */
-class CommentContentRebuildTest extends CommentTestBase {
-  public static function getInfo() {
-    return array(
-      'name' => 'Comment Rebuild',
-      'description' => 'Test to make sure the comment content is rebuilt.',
-      'group' => 'Comment',
-    );
-  }
-
-  /**
-   * Tests the rebuilding of comment's content arrays on calling comment_view().
-   */
-  function testCommentRebuild() {
-    // Update the comment settings so preview isn't required.
-    $this->drupalLogin($this->admin_user);
-    $this->setCommentSubject(TRUE);
-    $this->setCommentPreview(DRUPAL_OPTIONAL);
-    $this->drupalLogout();
-
-    // Log in as the web user and add the comment.
-    $this->drupalLogin($this->web_user);
-    $subject_text = $this->randomName();
-    $comment_text = $this->randomName();
-    $comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE);
-    $this->assertTrue($this->commentExists($comment), 'Comment found.');
-
-    // Add the property to the content array and then see if it still exists on build.
-    $comment->content['test_property'] = array('#value' => $this->randomString());
-    $built_content = comment_view($comment);
-
-    // This means that the content was rebuilt as the added test property no longer exists.
-    $this->assertFalse(isset($built_content['test_property']), 'Comment content was emptied before being built.');
-  }
-}
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentDefaultFormatterCacheTagsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..abf18c7ee51b0bf22b843fe40d3c388b9b58cd88
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentDefaultFormatterCacheTagsTest.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\comment\Tests\CommentDefaultFormatterCacheTagsTest.
+ */
+
+namespace Drupal\comment\Tests;
+
+use Drupal\comment\CommentInterface;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests CommentDefaultFormatter's cache tag bubbling.
+ */
+class CommentDefaultFormatterCacheTagsTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('entity_test', 'comment');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Comment list cache tags',
+      'description' => 'Tests the bubbling up of comment cache tags when using the Comment list formatter on an entity.',
+      'group' => 'Comment',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->drupalLogin($this->drupalCreateUser(array(
+      'access comments',
+    )));
+
+    // Set up a field, so that the entity that'll be referenced bubbles up a
+    // cache tag when rendering it entirely.
+    entity_create('field_config', array(
+      'name' => 'comment',
+      'entity_type' => 'entity_test',
+      'type' => 'comment',
+      'settings' => array(),
+    ))->save();
+    entity_create('field_instance_config', array(
+      'entity_type' => 'entity_test',
+      'bundle' => 'entity_test',
+      'field_name' => 'comment',
+      'label' => 'Comment',
+      'settings' => array(),
+    ))->save();
+    entity_get_display('entity_test', 'entity_test', 'default')
+      ->setComponent('comment', array(
+        'type' => 'comment_default',
+        'settings' => array(),
+      ))
+      ->save();
+  }
+
+  /**
+   * Tests the bubbling of cache tags.
+   */
+  public function testCacheTags() {
+    // Create the entity that will be commented upon.
+    $commented_entity = entity_create('entity_test', array('name' => $this->randomName()));
+    $commented_entity->save();
+
+    // Verify cache tags on the rendered entity before it has comments.
+    $build = \Drupal::entityManager()
+      ->getViewBuilder('entity_test')
+      ->view($commented_entity);
+    drupal_render($build);
+    $expected_cache_tags = array(
+      'entity_test_view' => TRUE,
+      'entity_test' => array(1 => $commented_entity->id()),
+    );
+    $this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags before it has comments.');
+
+    // Create a comment on that entity..
+    $comment = entity_create('comment', array(
+      'subject' => 'Llama',
+      'comment_body' => array(
+        'value' => 'Llamas are cool!',
+        'format' => 'plain_text',
+      ),
+      'entity_id' => $commented_entity->id(),
+      'entity_type' => 'entity_test',
+      'field_name' => 'comment',
+      'status' => CommentInterface::PUBLISHED,
+    ));
+    $comment->save();
+
+    // Load commented entity so comment_count gets computed.
+    // @todo remove the $reset = TRUE parameter after
+    //   https://drupal.org/node/597236 lands, it's a temporary work-around.
+    $commented_entity = entity_load('entity_test', $commented_entity->id(), TRUE);
+
+    // Verify cache tags on the rendered entity before it has comments.
+    $build = \Drupal::entityManager()
+      ->getViewBuilder('entity_test')
+      ->view($commented_entity);
+    drupal_render($build);
+    $expected_cache_tags = array(
+      'entity_test_view' => TRUE,
+      'entity_test' => array(1 => $commented_entity->id()),
+      'comment_view' => TRUE,
+      'comment' => array(1 => $comment->id()),
+      'filter_format' => array(
+        'plain_text' => 'plain_text',
+      ),
+    );
+    $this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags when it has comments.');
+  }
+
+}
diff --git a/core/modules/contact/lib/Drupal/contact/MessageViewBuilder.php b/core/modules/contact/lib/Drupal/contact/MessageViewBuilder.php
index f5eedb7cb9265801e2ceba965343bac8ec7f491f..4cbbd2c172085b53e257abc977d9af72e26d711e 100644
--- a/core/modules/contact/lib/Drupal/contact/MessageViewBuilder.php
+++ b/core/modules/contact/lib/Drupal/contact/MessageViewBuilder.php
@@ -20,14 +20,14 @@ class MessageViewBuilder extends EntityViewBuilder {
   /**
    * {@inheritdoc}
    */
-  public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
-    parent::buildContent($entities, $displays, $view_mode, $langcode);
+  public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
+    parent::buildComponents($build, $entities, $displays, $view_mode, $langcode);
 
-    foreach ($entities as $entity) {
+    foreach ($entities as $id => $entity) {
       // Add the message extra field, if enabled.
       $display = $displays[$entity->bundle()];
       if ($entity->getMessage() && $display->getComponent('message')) {
-        $entity->content['message'] = array(
+        $build[$id]['message'] = array(
           '#type' => 'item',
           '#title' => t('Message'),
           '#markup' => String::checkPlain($entity->getMessage()),
diff --git a/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php b/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php
index fd1b7f7229dfb753d4c703dad05f802ea0ab1261..0302c72a61d477faab18f6272dc9c8b9f8d96276 100644
--- a/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php
+++ b/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php
@@ -459,8 +459,8 @@ protected function renderTestEntity($id, $view_mode = 'full', $reset = TRUE) {
     }
     $entity = entity_load('entity_test', $id);
     $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
-    $entity->content = $display->build($entity);
-    $output = drupal_render($entity->content);
+    $build = $display->build($entity);
+    $output = drupal_render($build);
     $this->drupalSetContent($output);
     $this->verbose($output);
   }
diff --git a/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php b/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php
index e77c440bc26ae26aff23e2c0cde6d33fee4f6a79..3dcc55d9fb0b563d9dfe0709ab1ca84df5214836 100644
--- a/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php
+++ b/core/modules/entity/lib/Drupal/entity/Entity/EntityViewDisplay.php
@@ -209,9 +209,9 @@ public function build(ContentEntityInterface $entity) {
    * {@inheritdoc}
    */
   public function buildMultiple(array $entities) {
-    $build = array();
+    $build_list = array();
     foreach ($entities as $key => $entity) {
-      $build[$key] = array();
+      $build_list[$key] = array();
     }
 
     // Run field formatters.
@@ -230,8 +230,8 @@ public function buildMultiple(array $entities) {
         // Then let the formatter build the output for each entity.
         foreach ($entities as $key => $entity) {
           $items = $entity->get($field_name);
-          $build[$key][$field_name] = $formatter->view($items);
-          $build[$key][$field_name]['#access'] = $items->access('view');
+          $build_list[$key][$field_name] = $formatter->view($items);
+          $build_list[$key][$field_name]['#access'] = $items->access('view');
         }
       }
     }
@@ -239,8 +239,8 @@ public function buildMultiple(array $entities) {
     foreach ($entities as $key => $entity) {
       // Assign the configured weights.
       foreach ($this->getComponents() as $name => $options) {
-        if (isset($build[$key][$name])) {
-          $build[$key][$name]['#weight'] = $options['weight'];
+        if (isset($build_list[$key][$name])) {
+          $build_list[$key][$name]['#weight'] = $options['weight'];
         }
       }
 
@@ -250,10 +250,10 @@ public function buildMultiple(array $entities) {
         'view_mode' => $this->originalMode,
         'display' => $this,
       );
-      \Drupal::moduleHandler()->alter('entity_display_build', $build[$key], $context);
+      \Drupal::moduleHandler()->alter('entity_display_build', $build_list[$key], $context);
     }
 
-    return $build;
+    return $build_list;
   }
 
 }
diff --git a/core/modules/entity/lib/Drupal/entity/EntityDisplayBase.php b/core/modules/entity/lib/Drupal/entity/EntityDisplayBase.php
index dd1bfa0ed754f5f2e92bae0b279b931e578edb62..4b25e76b433ec60dbae541296d17279c197208c8 100644
--- a/core/modules/entity/lib/Drupal/entity/EntityDisplayBase.php
+++ b/core/modules/entity/lib/Drupal/entity/EntityDisplayBase.php
@@ -365,16 +365,23 @@ protected function getFieldDefinitions() {
 
     if (!isset($this->fieldDefinitions)) {
       $definitions = \Drupal::entityManager()->getFieldDefinitions($this->targetEntityType, $this->bundle);
-
-      // The display only cares about fields that specify display options.
-      // Discard base fields that are not rendered through formatters / widgets.
-      $display_context = $this->displayContext;
-      $this->fieldDefinitions = array_filter($definitions, function (FieldDefinitionInterface $definition) use ($display_context) {
-        return $definition->getDisplayOptions($display_context);
-      });
+      $this->fieldDefinitions = array_filter($definitions, array($this, 'fieldHasDisplayOptions'));
     }
 
     return $this->fieldDefinitions;
   }
 
+  /**
+   * Determines if a field has options for a given display.
+   *
+   * @param FieldDefinitionInterface $definition
+   *   A field instance definition.
+   * @return array|null
+   */
+  private function fieldHasDisplayOptions(FieldDefinitionInterface $definition) {
+    // The display only cares about fields that specify display options.
+    // Discard base fields that are not rendered through formatters / widgets.
+    return $definition->getDisplayOptions($this->displayContext);
+  }
+
 }
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
index 623140f3de6fa559fa47592d0a5c1613433cf1d1..410a2565d3eb4a7313963d8d4d47c86d4165d94e 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
@@ -93,9 +93,22 @@ public function viewElements(FieldItemListInterface $items) {
       }
 
       if (!empty($item->target_id)) {
-        $entity = clone $item->entity;
-        unset($entity->content);
-        $elements[$delta] = entity_view($entity, $view_mode, $item->getLangcode());
+        // The viewElements() method of entity field formatters is run
+        // during the #pre_render phase of rendering an entity. A formatter
+        // builds the content of the field in preparation for theming.
+        // All entity cache tags must be available after the #pre_render phase.
+        // This field formatter is highly exceptional: it renders *another*
+        // entity and this referenced entity has its own #pre_render
+        // callbacks. In order collect the cache tags associated with the
+        // referenced entity it must be passed to drupal_render() so that its
+        // #pre_render callbacks are invoked and its full build array is
+        // assembled. Rendering the referenced entity in place here will allow
+        // its cache tags to be bubbled up and included with those of the
+        // main entity when cache tags are collected for a renderable array
+        // in drupal_render().
+        $referenced_entity_build = entity_view($item->entity, $view_mode, $item->getLangcode());
+        drupal_render($referenced_entity_build, TRUE);
+        $elements[$delta] = $referenced_entity_build;
 
         if (empty($links) && isset($result[$delta][$target_type][$item->target_id]['links'])) {
           // Hide the element links.
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php
index 6df0d11a9ac90666bf16844bd46a61b8e29060a3..6d4c28dcec7edfd7246530559136e1d25b924374 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceIdFormatter.php
@@ -8,6 +8,7 @@
 namespace Drupal\entity_reference\Plugin\Field\FieldFormatter;
 
 use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Component\Utility\String;
 
 /**
  * Plugin implementation of the 'entity reference ID' formatter.
@@ -35,7 +36,17 @@ public function viewElements(FieldItemListInterface $items) {
         continue;
       }
       if (!empty($item->entity) && !empty($item->target_id)) {
-        $elements[$delta] = array('#markup' => check_plain($item->target_id));
+        /** @var $referenced_entity \Drupal\Core\Entity\EntityInterface */
+        $referenced_entity = $item->entity;
+        $elements[$delta] = array(
+          '#markup' => String::checkPlain($item->target_id),
+          // Create a cache tag entry for the referenced entity. In the case
+          // that the referenced entity is deleted, the cache for referring
+          // entities must be cleared.
+          '#cache' => array(
+            'tags' => $referenced_entity->getCacheTag(),
+          ),
+        );
       }
     }
 
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php
index 7cdac189ec60ddb67ff1f49e9b7354f132013d7b..bc305cd2b0e264e9a993cb2e249b781b72f40181 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldFormatter/EntityReferenceLabelFormatter.php
@@ -79,6 +79,7 @@ public function viewElements(FieldItemListInterface $items) {
         else {
           $elements[$delta] = array('#markup' => check_plain($label));
         }
+        $elements[$delta]['#cache']['tags'] = $referenced_entity->getCacheTag();
       }
     }
 
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFormatterTest.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFormatterTest.php
index 44bcca137f21c4883e5ec7056516540e383f9038..478c506d62f511cd0627afdd474df232ba4d2502 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFormatterTest.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFormatterTest.php
@@ -9,8 +9,6 @@
 
 use Drupal\system\Tests\Entity\EntityUnitTestBase;
 
-use Symfony\Component\HttpFoundation\Request;
-
 /**
  * Tests Entity Reference formatters.
  */
@@ -37,6 +35,13 @@ class EntityReferenceFormatterTest extends EntityUnitTestBase {
    */
   protected $fieldName = 'field_test';
 
+  /**
+   * The entity to be referenced in this test.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $referencedEntity = NULL;
+
   /**
    * Modules to enable.
    *
@@ -55,7 +60,41 @@ public static function getInfo() {
   public function setUp() {
     parent::setUp();
 
+    $this->installConfig(array('filter'));
+
     entity_reference_create_instance($this->entityType, $this->bundle, $this->fieldName, 'Field test', $this->entityType);
+
+    // Set up a field, so that the entity that'll be referenced bubbles up a
+    // cache tag when rendering it entirely.
+    entity_create('field_config', array(
+      'name' => 'body',
+      'entity_type' => $this->entityType,
+      'type' => 'text',
+      'settings' => array(),
+    ))->save();
+    entity_create('field_instance_config', array(
+      'entity_type' => $this->entityType,
+      'bundle' => $this->bundle,
+      'field_name' => 'body',
+      'label' => 'Body',
+      'settings' => array(
+        'text_processing' => TRUE,
+      ),
+    ))->save();
+    entity_get_display($this->entityType, $this->bundle, 'default')
+      ->setComponent('body', array(
+        'type' => 'text_default',
+        'settings' => array(),
+      ))
+      ->save();
+
+    // Create the entity to be referenced.
+    $this->referencedEntity = entity_create($this->entityType, array('name' => $this->randomName()));
+    $this->referencedEntity->body = array(
+      'value' => '<p>Hello, world!</p>',
+      'format' => 'full_html',
+    );
+    $this->referencedEntity->save();
   }
 
   /**
@@ -64,15 +103,12 @@ public function setUp() {
   public function testAccess() {
     $field_name = $this->fieldName;
 
-    $entity_1 = entity_create($this->entityType, array('name' => $this->randomName()));
-    $entity_1->save();
-
-    $entity_2 = entity_create($this->entityType, array('name' => $this->randomName()));
-    $entity_2->save();
-    $entity_2->{$field_name}->entity = $entity_1;
+    $referencing_entity = entity_create($this->entityType, array('name' => $this->randomName()));
+    $referencing_entity->save();
+    $referencing_entity->{$field_name}->entity = $this->referencedEntity;
 
     // Assert user doesn't have access to the entity.
-    $this->assertFalse($entity_1->access('view'), 'Current user does not have access to view the referenced entity.');
+    $this->assertFalse($this->referencedEntity->access('view'), 'Current user does not have access to view the referenced entity.');
 
     $formatter_manager = $this->container->get('plugin.manager.field.formatter');
 
@@ -86,10 +122,69 @@ public function testAccess() {
         ->save();
 
       // Invoke entity view.
-      entity_view($entity_2, 'default');
+      entity_view($referencing_entity, 'default');
 
       // Verify the un-accessible item still exists.
-      $this->assertEqual($entity_2->{$field_name}->value, $entity_1->id(), format_string('The un-accessible item still exists after @name formatter was executed.', array('@name' => $name)));
+      $this->assertEqual($referencing_entity->{$field_name}->value, $this->referencedEntity->id(), format_string('The un-accessible item still exists after @name formatter was executed.', array('@name' => $name)));
     }
   }
+
+  /**
+   * Tests the ID formatter.
+   */
+  public function testIdFormatter() {
+    $formatter = 'entity_reference_entity_id';
+    $field_name = $this->fieldName;
+
+    // Create the entity that will have the entity reference field.
+    $referencing_entity = entity_create($this->entityType, array('name' => $this->randomName()));
+    $referencing_entity->save();
+    $referencing_entity->{$field_name}->entity = $this->referencedEntity;
+    $referencing_entity->{$field_name}->access = TRUE;
+
+    // Build the renderable array for the entity reference field.
+    $items = $referencing_entity->get($field_name);
+    $build = $items->view(array('type' => $formatter));
+
+    $this->assertEqual($build[0]['#markup'], $this->referencedEntity->id(), format_string('The markup returned by the @formatter formatter is correct.', array('@formatter' => $formatter)));
+    $expected_cache_tags = array(
+      $this->entityType => array($this->referencedEntity->id()),
+    );
+    $this->assertEqual($build[0]['#cache']['tags'], $expected_cache_tags, format_string('The @formatter formatter has the expected cache tags.', array('@formatter' => $formatter)));
+
+  }
+
+  /**
+   * Tests the entity formatter.
+   */
+  public function testEntityFormatter() {
+    $formatter = 'entity_reference_entity_view';
+    $field_name = $this->fieldName;
+
+    // Create the entity that will have the entity reference field.
+    $referencing_entity = entity_create($this->entityType, array('name' => $this->randomName()));
+    $referencing_entity->save();
+    $referencing_entity->{$field_name}->entity = $this->referencedEntity;
+    $referencing_entity->{$field_name}->access = TRUE;
+
+    // Build the renderable array for the entity reference field.
+    $items = $referencing_entity->get($field_name);
+    $build = $items->view(array('type' => $formatter));
+
+    $expected_rendered_body_field = '<div class="field field-entity-test--body field-name-body field-type-text field-label-above">
+      <div class="field-label">Body:&nbsp;</div>
+    <div class="field-items">
+          <div class="field-item"></div>
+      </div>
+</div>
+';
+    $this->assertEqual($build[0]['#markup'], 'default | ' . $this->referencedEntity->label() .  $expected_rendered_body_field, format_string('The markup returned by the @formatter formatter is correct.', array('@formatter' => $formatter)));
+    $expected_cache_tags = array(
+      $this->entityType . '_view' => TRUE,
+      $this->entityType => array($this->referencedEntity->id() => $this->referencedEntity->id()),
+      'filter_format' => array('full_html' => 'full_html'),
+    );
+    $this->assertEqual($build[0]['#cache']['tags'], $expected_cache_tags, format_string('The @formatter formatter has the expected cache tags.', array('@formatter' => $formatter)));
+  }
+
 }
diff --git a/core/modules/history/history.module b/core/modules/history/history.module
index 35e3aa1d31f538c6b07f7f586f34af1ca373c871..e7ab0736a0f6f1b4b47982f5b34ff0973620f75a 100644
--- a/core/modules/history/history.module
+++ b/core/modules/history/history.module
@@ -131,7 +131,7 @@ function history_cron() {
 /**
  * Implements hook_node_view_alter().
  */
-function history_node_view_alter(&$build, EntityInterface $node, EntityViewDisplayInterface $display) {
+function history_node_view_alter(array &$build, EntityInterface $node, EntityViewDisplayInterface $display) {
   // Update the history table, stating that this user viewed this node.
   if (($display->originalMode === 'full') && \Drupal::currentUser()->isAuthenticated()) {
     $build['#attached'] = array(
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDefaultImagesTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDefaultImagesTest.php
index c0fd5d7d9c86e7b50224cf0702e18994b35c570d..54be792579e0ea00c89c4b112105bed427707148 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDefaultImagesTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDefaultImagesTest.php
@@ -160,7 +160,7 @@ public function testDefaultImages() {
 
     // Confirm that the image default is shown for a new article node.
     $article = $this->drupalCreateNode(array('type' => 'article'));
-    $article_built = node_view($article);
+    $article_built = $this->drupalBuildEntityView($article);
     $this->assertEqual(
       $article_built[$field_name]['#items'][0]->target_id,
       $default_images['instance']->id(),
@@ -172,7 +172,7 @@ public function testDefaultImages() {
 
     // Confirm that the image default is shown for a new page node.
     $page = $this->drupalCreateNode(array('type' => 'page'));
-    $page_built = node_view($page);
+    $page_built = $this->drupalBuildEntityView($page);
     $this->assertEqual(
       $page_built[$field_name]['#items'][0]->target_id,
       $default_images['instance2']->id(),
@@ -198,8 +198,8 @@ public function testDefaultImages() {
     );
 
     // Reload the nodes and confirm the field instance defaults are used.
-    $article_built = node_view($article = node_load($article->id(), TRUE));
-    $page_built = node_view($page = node_load($page->id(), TRUE));
+    $article_built = $this->drupalBuildEntityView($article = node_load($article->id(), TRUE));
+    $page_built = $this->drupalBuildEntityView($page = node_load($page->id(), TRUE));
     $this->assertEqual(
       $article_built[$field_name]['#items'][0]->target_id,
       $default_images['instance']->id(),
@@ -234,8 +234,8 @@ public function testDefaultImages() {
     );
 
     // Reload the nodes.
-    $article_built = node_view($article = node_load($article->id(),  TRUE));
-    $page_built = node_view($page = node_load($page->id(), TRUE));
+    $article_built = $this->drupalBuildEntityView($article = node_load($article->id(),  TRUE));
+    $page_built = $this->drupalBuildEntityView($page = node_load($page->id(), TRUE));
 
     // Confirm the article uses the new default.
     $this->assertEqual(
@@ -269,8 +269,8 @@ public function testDefaultImages() {
     );
 
     // Reload the nodes.
-    $article_built = node_view($article = node_load($article->id(), TRUE));
-    $page_built = node_view($page = node_load($page->id(), TRUE));
+    $article_built = $this->drupalBuildEntityView($article = node_load($article->id(), TRUE));
+    $page_built = $this->drupalBuildEntityView($page = node_load($page->id(), TRUE));
     // Confirm the article uses the new field (not instance) default.
     $this->assertEqual(
       $article_built[$field_name]['#items'][0]->target_id,
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageThemeFunctionTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageThemeFunctionTest.php
index 6be3ee9fd9d89488ff9d6a990168d8ea9a71fb41..fb83895f36a424a3f8dc2b7229675e8cd299f72f 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageThemeFunctionTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageThemeFunctionTest.php
@@ -86,9 +86,9 @@ function testImageFormatterTheme() {
     $image = $this->imageFactory->get('public://example.jpg');
     $entity->save();
 
-    // Test using theme_image_formatter() with a NULL value for the alt option.
+    // Create the base element that we'll use in the tests below.
     $path = $this->randomName();
-    $element = array(
+    $base_element = array(
       '#theme' => 'image_formatter',
       '#image_style' => 'test',
       '#item' => $entity->image_test,
@@ -96,25 +96,30 @@ function testImageFormatterTheme() {
         'path' => $path,
       ),
     );
-    $this->drupalSetContent(render($element));
+
+    // Test using theme_image_formatter() with a NULL value for the alt option.
+    $element = $base_element;
+    $this->drupalSetContent(drupal_render($element));
     $elements = $this->xpath('//a[@href=:path]/img[@class="image-style-test" and @src=:url and @width=:width and @height=:height]', array(':path' => base_path() . $path, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()));
     $this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders with a NULL value for the alt option.');
 
     // Test using theme_image_formatter() without an image title, alt text, or
     // link options.
+    $element = $base_element;
     $element['#item']->alt = '';
-    $this->drupalSetContent(render($element));
+    $this->drupalSetContent(drupal_render($element));
     $elements = $this->xpath('//a[@href=:path]/img[@class="image-style-test" and @src=:url and @width=:width and @height=:height and @alt=""]', array(':path' => base_path() . $path, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()));
     $this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders without title, alt, or path options.');
 
     // Link the image to a fragment on the page, and not a full URL.
     $fragment = $this->randomName();
+    $element = $base_element;
     $element['#path']['path'] = '';
     $element['#path']['options'] = array(
       'external' => TRUE,
       'fragment' => $fragment,
     );
-    $this->drupalSetContent(render($element));
+    $this->drupalSetContent(drupal_render($element));
     $elements = $this->xpath('//a[@href=:fragment]/img[@class="image-style-test" and @src=:url and @width=:width and @height=:height and @alt=""]', array(':fragment' => '#' . $fragment, ':url' => $url, ':width' => $image->getWidth(), ':height' => $image->getHeight()));
     $this->assertEqual(count($elements), 1, 'theme_image_formatter() correctly renders a link fragment.');
   }
@@ -133,18 +138,22 @@ function testImageStyleTheme() {
     $style->save();
     $url = $style->buildUrl($original_uri);
 
-    $element = array(
+    // Create the base element that we'll use in the tests below.
+    $base_element = array(
       '#theme' => 'image_style',
       '#style_name' => 'image_test',
       '#uri' => $original_uri,
     );
-    $this->drupalSetContent(render($element));
+
+    $element = $base_element;
+    $this->drupalSetContent(drupal_render($element));
     $elements = $this->xpath('//img[@class="image-style-image-test" and @src=:url and @alt=""]', array(':url' => $url));
     $this->assertEqual(count($elements), 1, 'theme_image_style() renders an image correctly.');
 
     // Test using theme_image_style() with a NULL value for the alt option.
+    $element = $base_element;
     $element['#alt'] = NULL;
-    $this->drupalSetContent(render($element));
+    $this->drupalSetContent(drupal_render($element));
     $elements = $this->xpath('//img[@class="image-style-image-test" and @src=:url]', array(':url' => $url));
     $this->assertEqual(count($elements), 1, 'theme_image_style() renders an image correctly with a NULL value for the alt option.');
   }
diff --git a/core/modules/node/lib/Drupal/node/NodeViewBuilder.php b/core/modules/node/lib/Drupal/node/NodeViewBuilder.php
index 34ca5deac92514b52de6f1d11a2735232b1ee183..494183f2a9bf3dcb39fa8282ae0dd6360863e640 100644
--- a/core/modules/node/lib/Drupal/node/NodeViewBuilder.php
+++ b/core/modules/node/lib/Drupal/node/NodeViewBuilder.php
@@ -19,35 +19,43 @@ class NodeViewBuilder extends EntityViewBuilder {
   /**
    * {@inheritdoc}
    */
-  public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
-    $return = array();
+  public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
+    /** @var \Drupal\node\NodeInterface[] $entities */
     if (empty($entities)) {
-      return $return;
+      return;
     }
 
     // Attach user account.
-    user_attach_accounts($entities);
+    user_attach_accounts($build, $entities);
 
-    parent::buildContent($entities, $displays, $view_mode, $langcode);
+    parent::buildComponents($build, $entities, $displays, $view_mode, $langcode);
 
-    foreach ($entities as $entity) {
+    foreach ($entities as $id => $entity) {
       $bundle = $entity->bundle();
       $display = $displays[$bundle];
 
-      $entity->content['links'] = array(
-        '#type' => 'render_cache_placeholder',
-        '#callback' => '\Drupal\node\NodeViewBuilder::renderLinks',
-        '#context' => array(
-          'node_entity_id' => $entity->id(),
-          'view_mode' => $view_mode,
-          'langcode' => $langcode,
-          'in_preview' => !empty($entity->in_preview),
+      $callback = '\Drupal\node\NodeViewBuilder::renderLinks';
+      $context = array(
+        'node_entity_id' => $entity->id(),
+        'view_mode' => $view_mode,
+        'langcode' => $langcode,
+        'in_preview' => !empty($entity->in_preview),
+        'token' => drupal_render_cache_generate_token(),
+      );
+
+      $build[$id]['links'] = array(
+        '#post_render_cache' => array(
+          $callback => array(
+            $context,
+          ),
         ),
+        '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
       );
 
+
       // Add Language field text element to node render array.
       if ($display->getComponent('langcode')) {
-        $entity->content['langcode'] = array(
+        $build[$id]['langcode'] = array(
           '#type' => 'item',
           '#title' => t('Language'),
           '#markup' => $entity->language()->name,
@@ -68,6 +76,12 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
     if (isset($defaults['#cache']) && isset($entity->in_preview)) {
       unset($defaults['#cache']);
     }
+    else {
+      // The node 'submitted' info is not rendered in a standard way (renderable
+      // array) so we have to add a cache tag manually.
+      // @todo Delete this once https://drupal.org/node/2226493 lands.
+      $defaults['#cache']['tags']['user'][] = $entity->getOwnerId();
+    }
 
     return $defaults;
   }
@@ -77,6 +91,8 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
    *
    * Renders the links on a node.
    *
+   * @param array $element
+   *   The renderable array that contains the to be replaced placeholder.
    * @param array $context
    *   An array with the following keys:
    *   - node_entity_id: a node entity ID
@@ -87,7 +103,10 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
    * @return array
    *   A renderable array representing the node links.
    */
-  public static function renderLinks(array $context) {
+  public static function renderLinks(array $element, array $context) {
+    $callback = '\Drupal\node\NodeViewBuilder::renderLinks';
+    $placeholder = drupal_render_cache_generate_placeholder($callback, $context, $context['token']);
+
     $links = array(
       '#theme' => 'links__node',
       '#pre_render' => array('drupal_pre_render_links'),
@@ -105,8 +124,10 @@ public static function renderLinks(array $context) {
       );
       \Drupal::moduleHandler()->alter('node_links', $links, $entity, $hook_context);
     }
+    $markup = drupal_render($links);
+    $element['#markup'] = str_replace($placeholder, $markup, $element['#markup']);
 
-    return $links;
+    return $element;
   }
 
   /**
@@ -160,10 +181,6 @@ protected function alterBuild(array &$build, EntityInterface $entity, EntityView
         'metadata' => array('changed' => $entity->getChangedTime()),
       );
     }
-
-    // The node 'submitted' info is not rendered in a standard way (renderable
-    // array) so we have to add a cache tag manually.
-    $build['#cache']['tags']['user'][] = $entity->getOwnerId();
   }
 
 }
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php
deleted file mode 100644
index c2e8eba643343489b9dbdbcd0efa0888ac820683..0000000000000000000000000000000000000000
--- a/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\node\Tests\NodeBuildContentTest.
- */
-
-namespace Drupal\node\Tests;
-
-/**
- * Test to ensure that a node's content is always rebuilt.
- */
-class NodeBuildContentTest extends NodeTestBase {
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Rebuild content',
-      'description' => 'Test the rebuilding of content for different build modes.',
-      'group' => 'Node',
-    );
-  }
-
-  /**
-   * Ensures that content array is rebuilt on every call to node_build_content().
-   */
-  function testNodeRebuildContent() {
-    $node = $this->drupalCreateNode();
-
-    // Set a property in the content array so we can test for its existence later on.
-    $node->content['test_content_property'] = array(
-      '#value' => $this->randomString(),
-    );
-    $content = node_view($node);
-
-    // If the property doesn't exist it means the node->content was rebuilt.
-    $this->assertFalse(isset($content['test_content_property']), 'Node content was emptied prior to being built.');
-  }
-}
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php
index 0d3dc2a958ecd79ee8af4f6845cb44218d1b7757..962f2f844d4726407001bc7b9fb6712e2bc3ba69 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeCacheTagsTest.php
@@ -61,6 +61,8 @@ protected function createEntity() {
 
   /**
    * {@inheritdoc}
+   *
+   * Each node must have an author.
    */
   protected function getAdditionalCacheTagsForEntity(EntityInterface $node) {
     return array('user:' . $node->getOwnerId());
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeEntityViewModeAlterTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeEntityViewModeAlterTest.php
index 2fb9918520504f3b042bc00a0ec8d086bbda97e6..156e37d00604a3bb9a61713f0b11df843afea512 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeEntityViewModeAlterTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeEntityViewModeAlterTest.php
@@ -51,7 +51,7 @@ function testNodeViewModeChange() {
     $this->assertNoText('Data that should appear only in the body for the node.', 'Body text not present');
 
     // Test that the correct build mode has been set.
-    $build = node_view($node);
+    $build = $this->drupalBuildEntityView($node);
     $this->assertEqual($build['#view_mode'], 'teaser', 'The view mode has correctly been set to teaser.');
   }
 }
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRSSContentTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRSSContentTest.php
index 535789985b971f1e70196c98117cbd7c1bd51bc7..64f8a2a19a9d06cbe4b2e396b5a089123bd0f281 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeRSSContentTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeRSSContentTest.php
@@ -11,8 +11,8 @@
  * Ensures that data added to nodes by other modules appears in RSS feeds.
  *
  * Create a node, enable the node_test module to ensure that extra data is
- * added to the node->content array, then verify that the data appears on the
- * sitewide RSS feed at rss.xml.
+ * added to the node's renderable array, then verify that the data appears on
+ * the site-wide RSS feed at rss.xml.
  */
 class NodeRSSContentTest extends NodeTestBase {
 
diff --git a/core/modules/node/lib/Drupal/node/Tests/SummaryLengthTest.php b/core/modules/node/lib/Drupal/node/Tests/SummaryLengthTest.php
index 9ad3373f19327ca5c808ce50aaa5e10eb63f0637..8568d44bf634c606e07b89b974c04297b66fbc20 100644
--- a/core/modules/node/lib/Drupal/node/Tests/SummaryLengthTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/SummaryLengthTest.php
@@ -35,9 +35,8 @@ function testSummaryLength() {
     $web_user = $this->drupalCreateUser(array('access content', 'administer content types'));
     $this->loggedInUser = $web_user;
 
-    $controller = $this->container->get('entity.manager')->getViewBuilder('node');
     // Render the node as a teaser.
-    $content = $controller->view($node, 'teaser');
+    $content = $this->drupalBuildEntityView($node, 'teaser');
     $this->assertTrue(strlen($content['body'][0]['#markup']) < 600, 'Teaser is less than 600 characters long.');
     $this->drupalSetContent(drupal_render($content));
     // The string 'What is a Drupalism?' is between the 200th and 600th
@@ -55,7 +54,7 @@ function testSummaryLength() {
 
     // Render the node as a teaser again and check that the summary is now only
     // 200 characters in length and so does not include 'What is a Drupalism?'.
-    $content = $controller->view($node, 'teaser');
+    $content = $this->drupalBuildEntityView($node, 'teaser');
     $this->assertTrue(strlen($content['body'][0]['#markup']) < 200, 'Teaser is less than 200 characters long.');
     $this->drupalSetContent(drupal_render($content));
     $this->assertText($node->label());
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index 2004013f177273596b0a3a9985f3c40f760d9d97..8293618b78d1dbd6392be625e633ff29ea1d64f4 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -770,15 +770,16 @@ function hook_node_submit(\Drupal\node\NodeInterface $node, $form, &$form_state)
 /**
  * Act on a node that is being assembled before rendering.
  *
- * The module may add elements to $node->content prior to rendering.
- * The structure of $node->content is a renderable array as expected by
- * drupal_render().
+ * The module may add elements to a node's renderable array array prior to
+ * rendering.
  *
  * When $view_mode is 'rss', modules can also add extra RSS elements and
  * namespaces to $node->rss_elements and $node->rss_namespaces respectively for
  * the RSS item generated for this node.
  * For details on how this is used, see node_feed().
  *
+ * @param array &$build
+ *   A renderable array representing the node content.
  * @param \Drupal\node\NodeInterface $node
  *   The node that is being assembled for rendering.
  * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
@@ -794,12 +795,12 @@ function hook_node_submit(\Drupal\node\NodeInterface $node, $form, &$form_state)
  *
  * @ingroup node_api_hooks
  */
-function hook_node_view(\Drupal\node\NodeInterface $node, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
+function hook_node_view(array &$build, \Drupal\node\NodeInterface $node, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
   // Only do the extra work if the component is configured to be displayed.
   // This assumes a 'mymodule_addition' extra field has been defined for the
   // node type in hook_entity_extra_field_info().
   if ($display->getComponent('mymodule_addition')) {
-    $node->content['mymodule_addition'] = array(
+    $build['mymodule_addition'] = array(
       '#markup' => mymodule_addition($node),
       '#theme' => 'mymodule_my_additional_field',
     );
@@ -819,7 +820,7 @@ function hook_node_view(\Drupal\node\NodeInterface $node, \Drupal\Core\Entity\Di
  * node.html.twig. See drupal_render() and _theme() documentation respectively
  * for details.
  *
- * @param $build
+ * @param &$build
  *   A renderable array representing the node content.
  * @param \Drupal\node\NodeInterface $node
  *   The node being rendered.
@@ -832,7 +833,7 @@ function hook_node_view(\Drupal\node\NodeInterface $node, \Drupal\Core\Entity\Di
  *
  * @ingroup node_api_hooks
  */
-function hook_node_view_alter(&$build, \Drupal\node\NodeInterface $node, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
+function hook_node_view_alter(array &$build, \Drupal\node\NodeInterface $node, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
   if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
     // Change its weight.
     $build['an_additional_field']['#weight'] = -10;
diff --git a/core/modules/node/tests/modules/node_test/node_test.module b/core/modules/node/tests/modules/node_test/node_test.module
index ad3c211bde37f0eaafd1b65767c8f2e20a8fc23f..58624c372bb8cdc8ea09ff2d23ba04fe7149ed27 100644
--- a/core/modules/node/tests/modules/node_test/node_test.module
+++ b/core/modules/node/tests/modules/node_test/node_test.module
@@ -15,17 +15,16 @@
 /**
  * Implements hook_node_view().
  */
-function node_test_node_view(NodeInterface $node, EntityViewDisplayInterface $display, $view_mode) {
+function node_test_node_view(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, $view_mode) {
   if ($view_mode == 'rss') {
     // Add RSS elements and namespaces when building the RSS feed.
     $node->rss_elements[] = array(
       'key' => 'testElement',
       'value' => t('Value of testElement RSS element for node !nid.', array('!nid' => $node->id())),
     );
-    $node->rss_namespaces['xmlns:drupaltest'] = 'http://example.com/test-namespace';
 
     // Add content that should be displayed only in the RSS feed.
-    $node->content['extra_feed_content'] = array(
+    $build['extra_feed_content'] = array(
       '#markup' => '<p>' . t('Extra data that should appear only in the RSS feed for node !nid.', array('!nid' => $node->id())) . '</p>',
       '#weight' => 10,
     );
@@ -33,12 +32,21 @@ function node_test_node_view(NodeInterface $node, EntityViewDisplayInterface $di
 
   if ($view_mode != 'rss') {
     // Add content that should NOT be displayed in the RSS feed.
-    $node->content['extra_non_feed_content'] = array(
+    $build['extra_non_feed_content'] = array(
       '#markup' => '<p>' . t('Extra data that should appear everywhere except the RSS feed for node !nid.', array('!nid' => $node->id())) . '</p>',
     );
   }
 }
 
+/**
+ * Implements hook_ENTITY_TYPE_build_defaults_alter().
+ */
+function node_test_node_build_defaults_alter(array &$build, NodeInterface &$node, $view_mode = 'full', $langcode = NULL) {
+  if ($view_mode == 'rss') {
+    $node->rss_namespaces['xmlns:drupaltest'] = 'http://example.com/test-namespace';
+  }
+}
+
 /**
  * Implements hook_node_grants().
  */
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index 7cfbc58838f0f887f3cb14ff9c4df1cd94a4746e..12c54e529e546d1a821d69a56d66bbc550158a00 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -15,6 +15,7 @@
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\ConnectionNotDefinedException;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Session\AnonymousUserSession;
@@ -341,6 +342,53 @@ protected function drupalCreateContentType(array $values = array()) {
     return $type;
   }
 
+  /**
+   * Builds the renderable view of an entity.
+   *
+   * Entities postpone the composition of their renderable arrays to #pre_render
+   * functions in order to maximize cache efficacy. This means that the full
+   * rendable array for an entity is constructed in drupal_render(). Some tests
+   * require the complete renderable array for an entity outside of the
+   * drupal_render process in order to verify the presence of specific values.
+   * This method isolates the steps in the render process that produce an
+   * entity's renderable array.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to prepare a renderable array for.
+   * @param string $view_mode
+   *   (optional) The view mode that should be used to build the entity.
+   * @param null $langcode
+   *   (optional) For which language the entity should be prepared, defaults to
+   *   the current content language.
+   * @param bool $reset
+   *   (optional) Whether to clear the cache for this entity.
+   * @return array
+   *
+   * @see drupal_render()
+   */
+  protected function drupalBuildEntityView(EntityInterface $entity, $view_mode = 'full', $langcode = NULL, $reset = FALSE) {
+    $render_controller = $this->container->get('entity.manager')->getViewBuilder($entity->getEntityTypeId());
+    if ($reset) {
+      $render_controller->resetCache(array($entity->id()));
+    }
+    $elements = $render_controller->view($entity, $view_mode, $langcode);
+    // If the default values for this element have not been loaded yet, populate
+    // them.
+    if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
+      $elements += element_info($elements['#type']);
+    }
+
+    // Make any final changes to the element before it is rendered. This means
+    // that the $element or the children can be altered or corrected before the
+    // element is rendered into the final text.
+    if (isset($elements['#pre_render'])) {
+      foreach ($elements['#pre_render'] as $callable) {
+        $elements = call_user_func($callable, $elements);
+      }
+    }
+    return $elements;
+  }
+
   /**
    * Creates a block instance based on default settings.
    *
diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module
index 887718160563ef7b35e97d4e6b376efa12d2655a..4b2110cfb121e88f37e75a6843af259d2ec88184 100644
--- a/core/modules/statistics/statistics.module
+++ b/core/modules/statistics/statistics.module
@@ -48,11 +48,11 @@ function statistics_permission() {
 /**
  * Implements hook_node_view().
  */
-function statistics_node_view(EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
+function statistics_node_view(array &$build, EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
   if (!$node->isNew() && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) {
-    $node->content['statistics_content_counter']['#attached']['library'][] = 'statistics/drupal.statistics';
+    $build['statistics_content_counter']['#attached']['library'][] = 'statistics/drupal.statistics';
     $settings = array('data' => array('nid' => $node->id()), 'url' => url(drupal_get_path('module', 'statistics') . '/statistics.php'));
-    $node->content['statistics_content_counter']['#attached']['js'][] = array(
+    $build['statistics_content_counter']['#attached']['js'][] = array(
       'data' => array('statistics' => $settings),
       'type' => 'setting',
     );
diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php
index 282e4e3d15cf3e357185ceebf86c58679c59f387..50b9f6a5ee1c3047f260200010feb57a64ec1c0e 100644
--- a/core/modules/system/entity.api.php
+++ b/core/modules/system/entity.api.php
@@ -436,6 +436,8 @@ function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query
 /**
  * Act on entities being assembled before rendering.
  *
+ * @param &$build
+ *   A renderable array representing the entity content.
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
  * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
@@ -446,8 +448,8 @@ function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query
  * @param $langcode
  *   The language code used for rendering.
  *
- * The module may add elements to $entity->content prior to rendering. The
- * structure of $entity->content is a renderable array as expected by
+ * The module may add elements to $build prior to rendering. The
+ * structure of $build is a renderable array as expected by
  * drupal_render().
  *
  * @see hook_entity_view_alter()
@@ -455,12 +457,12 @@ function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query
  * @see hook_node_view()
  * @see hook_user_view()
  */
-function hook_entity_view(\Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
+function hook_entity_view(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
   // Only do the extra work if the component is configured to be displayed.
   // This assumes a 'mymodule_addition' extra field has been defined for the
   // entity bundle in hook_entity_extra_field_info().
   if ($display->getComponent('mymodule_addition')) {
-    $entity->content['mymodule_addition'] = array(
+    $build['mymodule_addition'] = array(
       '#markup' => mymodule_addition($entity),
       '#theme' => 'mymodule_my_additional_field',
     );
@@ -480,7 +482,7 @@ function hook_entity_view(\Drupal\Core\Entity\EntityInterface $entity, \Drupal\C
  * the particular entity type template, if there is one (e.g., node.html.twig).
  * See drupal_render() and _theme() for details.
  *
- * @param $build
+ * @param array &$build
  *   A renderable array representing the entity content.
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object being rendered.
@@ -494,7 +496,7 @@ function hook_entity_view(\Drupal\Core\Entity\EntityInterface $entity, \Drupal\C
  * @see hook_taxonomy_term_view_alter()
  * @see hook_user_view_alter()
  */
-function hook_entity_view_alter(&$build, Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
+function hook_entity_view_alter(array &$build, Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
   if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
     // Change its weight.
     $build['an_additional_field']['#weight'] = -10;
@@ -560,6 +562,54 @@ function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInter
   }
 }
 
+/**
+ * Alters entity renderable values before cache checking in drupal_render().
+ *
+ * Invoked for a specific entity type.
+ *
+ * The values in the #cache key of the renderable array are used to determine if
+ * a cache entry exists for the entity's rendered output. Ideally only values
+ * that pertain to caching should be altered in this hook.
+ *
+ * @param array &$build
+ *   A renderable array containing the entity's caching and view mode values.
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity that is being viewed.
+ * @param string $view_mode
+ *   The view_mode that is to be used to display the entity.
+ * @param string $langcode
+ *   The code of the language $entity is accessed in.
+ *
+ * @see drupal_render()
+ * @see \Drupal\Core\Entity\EntityViewBuilder
+ */
+function hook_ENTITY_TYPE_build_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $view_mode, $langcode) {
+
+}
+
+/**
+ * Alters entity renderable values before cache checking in drupal_render().
+ *
+ * The values in the #cache key of the renderable array are used to determine if
+ * a cache entry exists for the entity's rendered output. Ideally only values
+ * that pertain to caching should be altered in this hook.
+ *
+ * @param array &$build
+ *   A renderable array containing the entity's caching and view mode values.
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity that is being viewed.
+ * @param string $view_mode
+ *   The view_mode that is to be used to display the entity.
+ * @param string $langcode
+ *   The code of the language $entity is accessed in.
+ *
+ * @see drupal_render()
+ * @see \Drupal\Core\Entity\EntityViewBuilder
+ */
+function hook_entity_build_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $view_mode, $langcode) {
+
+}
+
 /**
  * Alters the settings used for displaying an entity.
  *
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
index 349a14ef51ae293d56e5c0b6cb0ab56c525b2678..caa80a224c2c0e6629beaa13eec72cce5bc404b8 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
@@ -472,7 +472,6 @@ function testDrupalRenderPostRenderCache() {
     $output = drupal_render($element);
     $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
     $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
-    $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.');
     $settings = $this->parseDrupalSettings(drupal_get_js());
     $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.');
     $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.');
@@ -490,7 +489,6 @@ function testDrupalRenderPostRenderCache() {
     $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
     $this->assertTrue(isset($element['#printed']), 'No cache hit');
     $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
-    $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.');
     $settings = $this->parseDrupalSettings(drupal_get_js());
     $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.');
     $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.');
@@ -514,7 +512,6 @@ function testDrupalRenderPostRenderCache() {
     $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
     $this->assertFalse(isset($element['#printed']), 'Cache hit');
     $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
-    $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.');
     $settings = $this->parseDrupalSettings(drupal_get_js());
     $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.');
     $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.');
@@ -532,7 +529,6 @@ function testDrupalRenderPostRenderCache() {
     $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
     $this->assertTrue(isset($element['#printed']), 'No cache hit');
     $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
-    $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.');
     $settings = $this->parseDrupalSettings(drupal_get_js());
     $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.');
     $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.');
@@ -596,7 +592,6 @@ function testDrupalRenderChildrenPostRenderCache() {
     $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
     $this->assertTrue(isset($element['#printed']), 'No cache hit');
     $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
-    $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.');
     $settings = $this->parseDrupalSettings(drupal_get_js());
     $expected_settings = $context_1 + $context_2 + $context_3;
     $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.');
@@ -642,7 +637,6 @@ function testDrupalRenderChildrenPostRenderCache() {
     $output = drupal_render($element);
     $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
     $this->assertFalse(isset($element['#printed']), 'Cache hit');
-    $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.');
     $settings = $this->parseDrupalSettings(drupal_get_js());
     $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.');
     $this->assertIdentical($settings['common_test'], $expected_settings, '#attached is modified; JavaScript settings for each #post_render_cache callback are added to page.');
@@ -655,7 +649,6 @@ function testDrupalRenderChildrenPostRenderCache() {
     $output = drupal_render($element);
     $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
     $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
-    $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.');
     $settings = $this->parseDrupalSettings(drupal_get_js());
     $expected_settings = $context_1 + $context_2 + $context_3;
     $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.');
@@ -677,7 +670,6 @@ function testDrupalRenderChildrenPostRenderCache() {
     $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
     $this->assertTrue(isset($element['#printed']), 'No cache hit');
     $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
-    $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.');
     $settings = $this->parseDrupalSettings(drupal_get_js());
     $expected_settings = $context_1 + $context_2 + $context_3;
     $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.');
@@ -752,7 +744,6 @@ function testDrupalRenderChildrenPostRenderCache() {
     $output = drupal_render($element);
     $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
     $this->assertFalse(isset($element['#printed']), 'Cache hit');
-    $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.');
     $settings = $this->parseDrupalSettings(drupal_get_js());
     $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.');
     $this->assertIdentical($settings['common_test'], $expected_settings, '#attached is modified; JavaScript settings for each #post_render_cache callback are added to page.');
@@ -765,7 +756,6 @@ function testDrupalRenderChildrenPostRenderCache() {
     $output = drupal_render($element);
     $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
     $this->assertFalse(isset($element['#printed']), 'Cache hit');
-    $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.');
     $settings = $this->parseDrupalSettings(drupal_get_js());
     $expected_settings = $context_2 + $context_3;
     $this->assertTrue(!isset($settings['foo']), 'Parent JavaScript setting is not added to the page.');
@@ -775,16 +765,22 @@ function testDrupalRenderChildrenPostRenderCache() {
     \Drupal::request()->setMethod($request_method);
   }
 
-
   /**
    * Tests post-render cache-integrated 'render_cache_placeholder' element.
    */
   function testDrupalRenderRenderCachePlaceholder() {
-    $context = array('bar' => $this->randomContextValue());
+    $context = array(
+      'bar' => $this->randomContextValue(),
+      'token' => drupal_render_cache_generate_token(),
+    );
+    $callback = 'common_test_post_render_cache_placeholder';
     $test_element = array(
-      '#type' => 'render_cache_placeholder',
-      '#context' => $context,
-      '#callback' => 'common_test_post_render_cache_placeholder',
+      '#post_render_cache' => array(
+        $callback => array(
+          $context
+        ),
+      ),
+      '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
       '#prefix' => '<foo>',
       '#suffix' => '</foo>'
     );
@@ -814,8 +810,7 @@ function testDrupalRenderRenderCachePlaceholder() {
     $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.');
 
     // GET request: validate cached data.
-    $tokens = array_keys($element['#post_render_cache']['common_test_post_render_cache_placeholder']);
-    $expected_token = $tokens[0];
+    $expected_token = $element['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token'];
     $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET'));
     $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
     // Parse unique token out of the cached markup.
@@ -830,10 +825,10 @@ function testDrupalRenderRenderCachePlaceholder() {
     $this->assertIdentical($token, $expected_token, 'The tokens are identical');
     // Verify the token is in the cached element.
     $expected_element = array(
-      '#markup' => '<foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" context="bar:' . $context['bar'] .';" token="'. $expected_token . '" /></foo>',
+      '#markup' => '<foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" context="bar:' . $context['bar'] . ';token:' . $expected_token . ';" token="'. $expected_token . '" /></foo>',
       '#post_render_cache' => array(
         'common_test_post_render_cache_placeholder' => array(
-          $expected_token => $context,
+          $context
         ),
       ),
       '#cache' => array('tags' => array()),
@@ -860,14 +855,21 @@ function testDrupalRenderRenderCachePlaceholder() {
    * element.
    */
   function testDrupalRenderChildElementRenderCachePlaceholder() {
-    $context = array('bar' => $this->randomContextValue());
     $container = array(
       '#type' => 'container',
     );
+    $context = array(
+      'bar' => $this->randomContextValue(),
+      'token' => drupal_render_cache_generate_token(),
+    );
+    $callback = 'common_test_post_render_cache_placeholder';
     $test_element = array(
-      '#type' => 'render_cache_placeholder',
-      '#context' => $context,
-      '#callback' => 'common_test_post_render_cache_placeholder',
+      '#post_render_cache' => array(
+        $callback => array(
+          $context
+        ),
+      ),
+      '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
       '#prefix' => '<foo>',
       '#suffix' => '</foo>'
     );
@@ -904,9 +906,9 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
     $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.');
 
     // GET request: validate cached data for child element.
-    $child_tokens = array_keys($element['test_element']['#post_render_cache']['common_test_post_render_cache_placeholder']);
-    $parent_tokens = array_keys($element['#post_render_cache']['common_test_post_render_cache_placeholder']);
-    $expected_token = $child_tokens[0];
+    $child_tokens = $element['test_element']['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token'];
+    $parent_tokens = $element['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token'];
+    $expected_token = $child_tokens;
     $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET'));
     $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
     // Parse unique token out of the cached markup.
@@ -921,10 +923,10 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
     $this->assertIdentical($token, $expected_token, 'The tokens are identical for the child element');
     // Verify the token is in the cached element.
     $expected_element = array(
-      '#markup' => '<foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" context="bar:' . $context['bar'] .';" token="'. $expected_token . '" /></foo>',
+      '#markup' => '<foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" context="bar:' . $context['bar'] . ';token:' . $expected_token . ';" token="'. $expected_token . '" /></foo>',
       '#post_render_cache' => array(
         'common_test_post_render_cache_placeholder' => array(
-          $expected_token => $context,
+          $context,
         ),
       ),
       '#cache' => array('tags' => array()),
@@ -946,10 +948,10 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
     $this->assertIdentical($token, $expected_token, 'The tokens are identical for the parent element');
     // Verify the token is in the cached element.
     $expected_element = array(
-      '#markup' => '<div><foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" context="bar:' . $context['bar'] .';" token="'. $expected_token . '" /></foo></div>' . "\n",
+      '#markup' => '<div><foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" context="bar:' . $context['bar'] . ';token:' . $expected_token . ';" token="'. $expected_token . '" /></foo></div>' . "\n",
       '#post_render_cache' => array(
         'common_test_post_render_cache_placeholder' => array(
-          $expected_token => $context,
+          $context,
         ),
       ),
       '#cache' => array('tags' => array()),
@@ -963,7 +965,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
     $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
     // Verify that the child element contains the correct
     // render_cache_placeholder markup.
-    $expected_token = $child_tokens[0];
+    $expected_token = $child_tokens;
     $dom = Html::load($cached_element['#markup']);
     $xpath = new \DOMXPath($dom);
     $nodes = $xpath->query('//*[@token]');
@@ -975,10 +977,10 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
     $this->assertIdentical($token, $expected_token, 'The tokens are identical for the child element');
     // Verify the token is in the cached element.
     $expected_element = array(
-      '#markup' => '<foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" context="bar:' . $context['bar'] .';" token="'. $expected_token . '" /></foo>',
+      '#markup' => '<foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" context="bar:' . $context['bar'] . ';token:' . $expected_token . ';" token="'. $expected_token . '" /></foo>',
       '#post_render_cache' => array(
         'common_test_post_render_cache_placeholder' => array(
-          $expected_token => $context,
+          $context,
         ),
       ),
       '#cache' => array('tags' => array()),
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
index e4a6a522b6c3ca24c0c376f87d18a6752acf8cc8..7e6506bdd160c5611b59cdbb6e9e54d3c28df2d9 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\String;
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
 
@@ -124,6 +125,20 @@ protected static function generateStandardizedInfo($entity_type_label, $group) {
    */
   abstract protected function createEntity();
 
+  /**
+   * Returns the additional (non-standard) cache tags for the tested entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to be tested, as created by createEntity().
+   * @return array
+   *   An array of the additional cache tags.
+   *
+   * @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
+   */
+  protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
+    return array();
+  }
+
   /**
    * Creates a referencing and a non-referencing entity for testing purposes.
    *
@@ -168,7 +183,7 @@ protected function createReferenceTestEntities($referenced_entity) {
       ),
     ))->save();
     entity_get_display($entity_type, $bundle, 'full')
-      ->setComponent($field_name, array('type' => 'entity_reference_label'))
+      ->setComponent($field_name, array('type' => 'entity_reference_entity_view'))
       ->save();
 
     // Create an entity that does reference the entity being tested.
@@ -221,9 +236,10 @@ public function testReferencedEntity() {
       'entity_test_view:1',
       'entity_test:' . $this->referencing_entity->id(),
       // Includes the main entity's cache tags, since this entity references it.
-      $cache_tag,
       $view_cache_tag,
+      $cache_tag,
     );
+    $referencing_entity_cache_tags = array_merge($referencing_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity));
     $non_referencing_entity_cache_tags = array(
       'entity_test_view:1',
       'entity_test:' . $this->non_referencing_entity->id(),
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
index 58ad4c379d197d1fbdde89b2cf1fad6174e6bf62..2086c14fd5c60a9a58878b80bee970bc56347563 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
@@ -490,19 +490,31 @@ function testLanguageFallback() {
 
     // Check that if the entity has no translation no fallback is applied.
     $entity2 = $controller->create(array('langcode' => $default_langcode));
+    // Get an view builder.
+    $controller = $this->entityManager->getViewBuilder($entity_type);
+    $entity2_build = $controller->view($entity2);
+    $entity2_output = drupal_render($entity2_build);
     $translation = $this->entityManager->getTranslationFromContext($entity2, $default_langcode);
-    $this->assertIdentical($entity2, $translation, 'When the entity has no translation no fallback is applied.');
+    $translation_build = $controller->view($translation);
+    $translation_output = drupal_render($translation_build);
+    $this->assertIdentical($entity2_output, $translation_output, 'When the entity has no translation no fallback is applied.');
 
     // Checks that entity translations are rendered properly.
     $controller = $this->entityManager->getViewBuilder($entity_type);
     $build = $controller->view($entity);
+    drupal_render($build);
     $this->assertEqual($build['label']['#markup'], $values[$current_langcode]['name'], 'By default the entity is rendered in the current language.');
+
     $langcodes = array_combine($this->langcodes, $this->langcodes);
     // We have no translation for the $langcode2 langauge, hence the expected
     // result is the topmost existing translation, that is $langcode.
     $langcodes[$langcode2] = $langcode;
     foreach ($langcodes as $desired => $expected) {
       $build = $controller->view($entity, 'full', $desired);
+      // Unset the #cache key so that a fresh render is produced with each pass,
+      // making the renderable array keys available to compare.
+      unset($build['#cache']);
+      drupal_render($build);
       $this->assertEqual($build['label']['#markup'], $values[$expected]['name'], 'The entity is rendered in the expected language.');
     }
   }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php
index 3d308a54778a82065b11b05ee71c10dcca6f25a2..230e48907b515340a8b50c3e5ea2148dade266b2 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityWithUriCacheTagsTestBase.php
@@ -8,7 +8,6 @@
 namespace Drupal\system\Tests\Entity;
 
 use Drupal\Core\Cache\Cache;
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
 
 /**
@@ -16,20 +15,6 @@
  */
 abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
 
-  /**
-   * Returns the additional (non-standard) cache tags for the tested entity.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to be tested, as created by createEntity().
-   * @return array
-   *   An array of the additional cache tags.
-   *
-   * @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
-   */
-  protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
-    return array();
-  }
-
   /**
    * Tests cache tags presence and invalidation of the entity at its URI.
    *
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigDebugMarkupTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigDebugMarkupTest.php
index 9c2105a04bb169b7f7f5bc2ca1630ac1f15a7b37..cd97dba67fae7b6fbc37a9b73a145266f42c17a5 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigDebugMarkupTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigDebugMarkupTest.php
@@ -48,7 +48,8 @@ function testTwigDebugMarkup() {
 
     // Create a node and test different features of the debug markup.
     $node = $this->drupalCreateNode();
-    $output = _theme('node', node_view($node));
+    $build = node_view($node);
+    $output = drupal_render($build);
     $this->assertTrue(strpos($output, '<!-- THEME DEBUG -->') !== FALSE, 'Twig debug markup found in theme output when debug is enabled.');
     $this->assertTrue(strpos($output, "CALL: _theme('node')") !== FALSE, 'Theme call information found.');
     $this->assertTrue(strpos($output, 'x node--1' . $extension . PHP_EOL . '   * node--page' . $extension . PHP_EOL . '   * node' . $extension) !== FALSE, 'Suggested template files found in order and node ID specific template shown as current template.');
@@ -58,7 +59,8 @@ function testTwigDebugMarkup() {
     // Create another node and make sure the template suggestions shown in the
     // debug markup are correct.
     $node2 = $this->drupalCreateNode();
-    $output = _theme('node', node_view($node2));
+    $build = node_view($node2);
+    $output = drupal_render($build);
     $this->assertTrue(strpos($output, '* node--2' . $extension . PHP_EOL . '   * node--page' . $extension . PHP_EOL . '   x node' . $extension) !== FALSE, 'Suggested template files found in order and base template shown as current template.');
 
     // Create another node and make sure the template suggestions shown in the
@@ -75,7 +77,8 @@ function testTwigDebugMarkup() {
     $this->rebuildContainer();
     $this->resetAll();
 
-    $output = _theme('node', node_view($node));
+    $build = node_view($node);
+    $output = drupal_render($build);
     $this->assertFalse(strpos($output, '<!-- THEME DEBUG -->') !== FALSE, 'Twig debug markup not found in theme output when debug is disabled.');
   }
 
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 8b4455b41f3e7cbe19bb9c1dbb8a59075b895009..6fd6ba45679cc3723f88ee40a7f28d60ed94b8c9 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -627,12 +627,6 @@ function system_element_info() {
     '#theme' => 'table',
   );
 
-  // Other elements.
-  $types['render_cache_placeholder'] = array(
-    '#callback' => '',
-    '#context' => array(),
-  );
-
   return $types;
 }
 
diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module
index 1a081df995e20c3b70056820b04f94e88d1d3605..2785be97f36b42c17e9ec52390575434296fed2e 100644
--- a/core/modules/system/tests/modules/common_test/common_test.module
+++ b/core/modules/system/tests/modules/common_test/common_test.module
@@ -197,6 +197,8 @@ function common_test_post_render_cache(array $element, array $context) {
 /**
  * #post_render_cache callback; replaces placeholder, extends #attached.
  *
+ * @param array $element
+ *   The renderable array that contains the to be replaced placeholder.
  * @param array $context
  *  An array with the following keys:
  *    - bar: contains a random string.
@@ -204,8 +206,9 @@ function common_test_post_render_cache(array $element, array $context) {
  * @return array
  *   A render array.
  */
-function common_test_post_render_cache_placeholder(array $context) {
-  $element = array(
+function common_test_post_render_cache_placeholder(array $element, array $context) {
+  $placeholder = drupal_render_cache_generate_placeholder(__FUNCTION__, $context, $context['token']);
+  $replace_element = array(
     '#markup' => '<bar>' . $context['bar'] . '</bar>',
     '#attached' => array(
       'js' => array(
@@ -218,6 +221,8 @@ function common_test_post_render_cache_placeholder(array $context) {
       ),
     ),
   );
+  $markup = drupal_render($replace_element);
+  $element['#markup'] = str_replace($placeholder, $markup, $element['#markup']);
 
   return $element;
 }
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestViewBuilder.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestViewBuilder.php
index 6805765130326fed20614460a25a33cd27c640d8..15f6b6d9077dec4902424d220efde9f7833c499e 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestViewBuilder.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestViewBuilder.php
@@ -19,17 +19,17 @@ class EntityTestViewBuilder extends EntityViewBuilder {
   /**
    * {@inheritdoc}
    */
-  public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
-    parent::buildContent($entities, $displays, $view_mode, $langcode);
+  public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
+    parent::buildComponents($build, $entities, $displays, $view_mode, $langcode);
 
-    foreach ($entities as $entity) {
-      $entity->content['label'] = array(
+    foreach ($entities as $id => $entity) {
+      $build[$id]['label'] = array(
         '#markup' => check_plain($entity->label()),
       );
-      $entity->content['separator'] = array(
+      $build[$id]['separator'] = array(
         '#markup' => ' | ',
       );
-      $entity->content['view_mode'] = array(
+      $build[$id]['view_mode'] = array(
         '#markup' => check_plain($view_mode),
       );
     }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Field/FieldFormatter/LinkFormatter.php
index eb0515c92fccbd5b7bf0b661c164c92763f4b620..856b73e0ad0b9933917d15477cb3df2cb561b886 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Field/FieldFormatter/LinkFormatter.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Field/FieldFormatter/LinkFormatter.php
@@ -51,6 +51,8 @@ public function viewElements(FieldItemListInterface $items) {
           // formatter output and should not be rendered in the field template.
           unset($item->_attributes);
         }
+
+        $elements[$delta]['#cache']['tags'] = $item->entity->getCacheTag();
       }
     }
 
diff --git a/core/modules/taxonomy/taxonomy.api.php b/core/modules/taxonomy/taxonomy.api.php
index 960cff299f7bba7c78fab98f18b29f88f26a5e27..9fa84c1db2bd6307bce06b76a4719744e6c05014 100644
--- a/core/modules/taxonomy/taxonomy.api.php
+++ b/core/modules/taxonomy/taxonomy.api.php
@@ -243,10 +243,11 @@ function hook_taxonomy_term_delete(Drupal\taxonomy\Term $term) {
 /**
  * Act on a taxonomy term that is being assembled before rendering.
  *
- * The module may add elements to $term->content prior to rendering. The
- * structure of $term->content is a renderable array as expected by
- * drupal_render().
- *
+ * The module may add elements to a taxonomy term's renderable array array prior
+ * to rendering.
+
+ * @param array &$build
+ *   A renderable array representing the taxonomy term content.
  * @param \Drupal\taxonomy\Entity\Term $term
  *   The term that is being assembled for rendering.
  * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
@@ -259,12 +260,12 @@ function hook_taxonomy_term_delete(Drupal\taxonomy\Term $term) {
  *
  * @see hook_entity_view()
  */
-function hook_taxonomy_term_view(\Drupal\taxonomy\Entity\Term $term, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
+function hook_taxonomy_term_view(array &$build, \Drupal\taxonomy\Entity\Term $term, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
   // Only do the extra work if the component is configured to be displayed.
   // This assumes a 'mymodule_addition' extra field has been defined for the
   // vocabulary in hook_entity_extra_field_info().
   if ($display->getComponent('mymodule_addition')) {
-    $term->content['mymodule_addition'] = array(
+    $build['mymodule_addition'] = array(
       '#markup' => mymodule_addition($term),
       '#theme' => 'mymodule_my_additional_field',
     );
@@ -284,7 +285,7 @@ function hook_taxonomy_term_view(\Drupal\taxonomy\Entity\Term $term, \Drupal\Cor
  * hook_preprocess_HOOK() for taxonomy-term.html.twig. See drupal_render() and
  * _theme() documentation respectively for details.
  *
- * @param $build
+ * @param array &$build
  *   A renderable array representing the taxonomy term content.
  * @param \Drupal\taxonomy\Entity\Term $term
  *   The taxonomy term being rendered.
@@ -294,7 +295,7 @@ function hook_taxonomy_term_view(\Drupal\taxonomy\Entity\Term $term, \Drupal\Cor
  *
  * @see hook_entity_view_alter()
  */
-function hook_taxonomy_term_view_alter(&$build, \Drupal\taxonomy\Entity\Term $term, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
+function hook_taxonomy_term_view_alter(array &$build, \Drupal\taxonomy\Entity\Term $term, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
   if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
     // Change its weight.
     $build['an_additional_field']['#weight'] = -10;
diff --git a/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextDefaultFormatter.php b/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextDefaultFormatter.php
index 6f33c160a216cee9501efcc8e87fca4f215fd112..5e0f80b434fd9f4f6c7cb1eff6a34d212cf6ab1d 100644
--- a/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextDefaultFormatter.php
+++ b/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextDefaultFormatter.php
@@ -35,7 +35,16 @@ public function viewElements(FieldItemListInterface $items) {
     $elements = array();
 
     foreach ($items as $delta => $item) {
-      $elements[$delta] = array('#markup' => $item->processed);
+      $elements[$delta] = array(
+        '#markup' => $item->processed, 
+        '#cache' => array(
+          'tags' => array(
+            'filter_format' => array(
+              $item->format,
+            ),
+          ),
+        ),
+      );
     }
 
     return $elements;
diff --git a/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php b/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php
index 0ebd904e7b1834b8ae6101aaa23f61dd2b29ae9f..d4b3c34a9a20dd1dc92cbd3607035e1f0c7885cf 100644
--- a/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php
+++ b/core/modules/text/lib/Drupal/text/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php
@@ -79,7 +79,16 @@ public function viewElements(FieldItemListInterface $items) {
         $output = $item->processed;
         $output = text_summary($output, $text_processing ? $item->format : NULL, $this->getSetting('trim_length'));
       }
-      $elements[$delta] = array('#markup' => $output);
+      $elements[$delta] = array(
+        '#markup' => $output,
+        '#cache' => array(
+          'tags' => array(
+            'filter_format' => array(
+              $item->format,
+            ),
+          ),
+        ),
+      );
     }
 
     return $elements;
diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php
index b71a65fd4eb6db779d38e38d7160b5d3bd8a7432..ab6e2b6b7aa19f8b7e43db47f4092b90aaa000aa 100644
--- a/core/modules/user/user.api.php
+++ b/core/modules/user/user.api.php
@@ -306,8 +306,10 @@ function hook_user_logout($account) {
  * The user's account information is being displayed.
  *
  * The module should format its custom additions for display and add them to the
- * $account->content array.
+ * $build array.
  *
+ * @param array &$build
+ *   A renderable array representing the user content.
  * @param \Drupal\user\UserInterface $account
  *   The user object on which the operation is being performed.
  * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
@@ -321,12 +323,12 @@ function hook_user_logout($account) {
  * @see hook_user_view_alter()
  * @see hook_entity_view()
  */
-function hook_user_view(\Drupal\user\UserInterface $account, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
+function hook_user_view(array &$build, \Drupal\user\UserInterface $account, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
   // Only do the extra work if the component is configured to be displayed.
   // This assumes a 'mymodule_addition' extra field has been defined for the
   // user entity type in hook_entity_extra_field_info().
   if ($display->getComponent('mymodule_addition')) {
-    $account->content['mymodule_addition'] = array(
+    $build['mymodule_addition'] = array(
       '#markup' => mymodule_addition($account),
       '#theme' => 'mymodule_my_additional_field',
     );
@@ -346,7 +348,7 @@ function hook_user_view(\Drupal\user\UserInterface $account, \Drupal\Core\Entity
  * user.html.twig. See drupal_render() and _theme() documentation
  * respectively for details.
  *
- * @param $build
+ * @param array &$build
  *   A renderable array representing the user.
  * @param \Drupal\user\UserInterface $account
  *   The user account being rendered.
@@ -357,7 +359,7 @@ function hook_user_view(\Drupal\user\UserInterface $account, \Drupal\Core\Entity
  * @see user_view()
  * @see hook_entity_view_alter()
  */
-function hook_user_view_alter(&$build, \Drupal\user\UserInterface $account, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
+function hook_user_view_alter(array &$build, \Drupal\user\UserInterface $account, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
   // Check for the existence of a field added by another module.
   if (isset($build['an_additional_field'])) {
     // Change its weight.
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 2418ee70ade69087ae16c6d0406c0fbce2e90dc5..08b5de3d40c6de1552c7f2220f1cd0b9d3d036e5 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -165,13 +165,15 @@ function user_uri($user) {
 /**
  * Populates $entity->account for each prepared entity.
  *
- * Called by Drupal\Core\Entity\EntityViewBuilderInterface::buildContent()
+ * Called by Drupal\Core\Entity\EntityViewBuilderInterface::buildComponents()
  * implementations.
  *
+ * @param array &$build
+ *   A renderable array representing the entity content.
  * @param \Drupal\user\EntityOwnerInterface[] $entities
  *   The entities keyed by entity ID.
  */
-function user_attach_accounts(array $entities) {
+function user_attach_accounts(array &$build, array $entities) {
   $uids = array();
   foreach ($entities as $entity) {
     $uids[] = $entity->getOwnerId();
@@ -503,9 +505,9 @@ function user_permission() {
 /**
  * Implements hook_user_view().
  */
-function user_user_view(UserInterface $account, EntityViewDisplayInterface $display) {
+function user_user_view(array &$build, UserInterface $account, EntityViewDisplayInterface $display) {
   if ($display->getComponent('member_for')) {
-    $account->content['member_for'] = array(
+    $build['member_for'] = array(
       '#type' => 'item',
       '#title' => t('Member for'),
       '#markup' => format_interval(REQUEST_TIME - $account->getCreatedTime()),
diff --git a/core/modules/views/lib/Drupal/views/Entity/Render/CurrentLanguageRenderer.php b/core/modules/views/lib/Drupal/views/Entity/Render/CurrentLanguageRenderer.php
index 8090ce6c1e02a2f033147d163a83285b6e7da875..fb917874df11c4cb7bcad8bfdd25c4eafe7a6c07 100644
--- a/core/modules/views/lib/Drupal/views/Entity/Render/CurrentLanguageRenderer.php
+++ b/core/modules/views/lib/Drupal/views/Entity/Render/CurrentLanguageRenderer.php
@@ -7,8 +7,20 @@
 
 namespace Drupal\views\Entity\Render;
 
+use Drupal\views\ResultRow;
+
 /**
  * Renders entities in the current language.
  */
 class CurrentLanguageRenderer extends RendererBase {
+
+  /**
+   * Returns NULL so that the current language is used.
+   *
+   * @param \Drupal\views\ResultRow $row
+   *   The result row.
+   */
+  protected function getLangcode(ResultRow $row) {
+  }
+
 }
diff --git a/core/modules/views/lib/Drupal/views/Entity/Render/DefaultLanguageRenderer.php b/core/modules/views/lib/Drupal/views/Entity/Render/DefaultLanguageRenderer.php
index 8bd9e9450c0f22b6ec12f559ba66e8983b515a50..18875f5665f5be12b052bdd8fd93523fcad5412e 100644
--- a/core/modules/views/lib/Drupal/views/Entity/Render/DefaultLanguageRenderer.php
+++ b/core/modules/views/lib/Drupal/views/Entity/Render/DefaultLanguageRenderer.php
@@ -14,61 +14,6 @@
  */
 class DefaultLanguageRenderer extends RendererBase {
 
-  /**
-   * {@inheritdoc}
-   */
-  public function preRender(array $result) {
-    /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
-    $entities = array();
-    $langcodes = array();
-
-    /** @var \Drupal\views\ResultRow $row */
-    foreach ($result as $row) {
-      $entity = $row->_entity;
-      $entity->view = $this->view;
-      $langcodes[] = $langcode = $this->getLangcode($row);
-      $entities[$entity->id()][$langcode] = $entity;
-    }
-    $count_langcodes = count(array_unique($langcodes));
-
-    $view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
-
-    if ($count_langcodes > 1) {
-      // Render each entity separate if we do have more than one translation.
-      // @todo It should be possible to use viewMultiple even if you get
-      //   more than one language. See https://drupal.org/node/2073217.
-      foreach ($entities as $entity_translation) {
-        foreach ($entity_translation as $langcode => $entity) {
-          $entity = $entity->getTranslation($langcode);
-          $this->build[$entity->id()][$langcode] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $langcode);
-        }
-      }
-    }
-    else {
-      $langcode = reset($langcodes);
-      $entity_translations = array();
-      foreach ($entities as $entity_translation) {
-        $entity = $entity_translation[$langcode];
-        $entity_translations[$entity->id()] = $entity->hasTranslation($langcode) ? $entity->getTranslation($langcode) : $entity;
-      }
-      $this->build = $view_builder->viewMultiple($entity_translations, $this->view->rowPlugin->options['view_mode'], $langcode);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function render(ResultRow $row) {
-    $entity_id = $row->_entity->id();
-    $langcode = $this->getLangcode($row);
-    if (isset($this->build[$entity_id][$langcode])) {
-      return $this->build[$entity_id][$langcode];
-    }
-    else {
-      return $this->build[$entity_id];
-    }
-  }
-
   /**
    * Returns the language code associated to the given row.
    *
diff --git a/core/modules/views/lib/Drupal/views/Entity/Render/RendererBase.php b/core/modules/views/lib/Drupal/views/Entity/Render/RendererBase.php
index a3b211ff5da24770da6c586ab0ed7fbca0eb7632..b3590c2c444a9f5d632f67d64fe24198061a4baf 100644
--- a/core/modules/views/lib/Drupal/views/Entity/Render/RendererBase.php
+++ b/core/modules/views/lib/Drupal/views/Entity/Render/RendererBase.php
@@ -67,18 +67,14 @@ public function query(QueryPluginBase $query) {
    *   The full array of results from the query.
    */
   public function preRender(array $result) {
-    /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
-    $entities = array();
+    $view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
 
     /** @var \Drupal\views\ResultRow $row */
     foreach ($result as $row) {
       $entity = $row->_entity;
       $entity->view = $this->view;
-      $entities[$entity->id()] = $entity;
+      $this->build[$entity->id()] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $this->getLangcode($row));
     }
-
-    $view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
-    $this->build = $view_builder->viewMultiple($entities, $this->view->rowPlugin->options['view_mode']);
   }
 
   /**
diff --git a/core/modules/views/lib/Drupal/views/Entity/Render/TranslationLanguageRenderer.php b/core/modules/views/lib/Drupal/views/Entity/Render/TranslationLanguageRenderer.php
index 362b1425dde398f85f06965c9dcefafb1ccbee65..e65190a2f912d0029f8a9b76e280e8dcdfc69505 100644
--- a/core/modules/views/lib/Drupal/views/Entity/Render/TranslationLanguageRenderer.php
+++ b/core/modules/views/lib/Drupal/views/Entity/Render/TranslationLanguageRenderer.php
@@ -39,6 +39,30 @@ public function query(QueryPluginBase $query) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function preRender(array $result) {
+    $view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
+
+    /** @var \Drupal\views\ResultRow $row */
+    foreach ($result as $row) {
+      $entity = $row->_entity;
+      $entity->view = $this->view;
+      $langcode = $this->getLangcode($row);
+      $this->build[$entity->id()][$langcode] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $this->getLangcode($row));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render(ResultRow $row) {
+    $entity_id = $row->_entity->id();
+    $langcode = $this->getLangcode($row);
+    return $this->build[$entity_id][$langcode];
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/views/lib/Drupal/views/Tests/Entity/RowEntityRenderersTest.php b/core/modules/views/lib/Drupal/views/Tests/Entity/RowEntityRenderersTest.php
index 1694fd098e582825515ae31bb7ed34c2335b0780..becbeaf6c9284813c0df3153107bdba84180d879 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Entity/RowEntityRenderersTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Entity/RowEntityRenderersTest.php
@@ -60,6 +60,9 @@ protected function setUp() {
     $this->installSchema('user', array('users'));
     $this->installConfig(array('node', 'language'));
 
+    // The node.view route must exist when nodes are rendered.
+    $this->container->get('router.builder')->rebuild();
+
     $this->langcodes = array(\Drupal::languageManager()->getDefaultLanguage()->id);
     for ($i = 0; $i < 2; $i++) {
       $langcode = 'l' . $i;
@@ -168,7 +171,7 @@ protected function assertTranslations($renderer_id, array $expected, $message =
     $result = TRUE;
     foreach ($view->result as $index => $row) {
       $build = $view->rowPlugin->render($row);
-      $output = drupal_render($build['title']);
+      $output = drupal_render($build);
       $result = strpos($output, $expected[$index]) !== FALSE;
       if (!$result) {
         break;