diff --git a/core/includes/common.inc b/core/includes/common.inc
index e523b1b..eb556fb 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3787,6 +3787,16 @@ 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);
@@ -3890,16 +3900,6 @@ 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 (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;
-    }
-  }
-
   // Cache the processed element if #cache is set.
   if (isset($elements['#cache'])) {
     drupal_render_cache_set($elements['#markup'], $elements);
@@ -3972,7 +3972,7 @@ function render(&$element) {
   }
   if (is_array($element)) {
     show($element);
-    return drupal_render($element);
+    return drupal_render($element, TRUE);
   }
   else {
     // Safe-guard for inappropriate use of render() on flat variables: return
@@ -4248,8 +4248,19 @@ function _drupal_render_process_post_render_cache(array &$elements) {
  * elements. This allows drupal_render() to execute all of them when the element
  * is retrieved from the render cache.
  *
- * @param array $elements
- *   The element to collect #post_render_cache from.
+ * Note: the theme system may render child elements directly (e.g. rendering a
+ * node causes its template to be rendered, which causes the node links to be
+ * drupal_render()ed). On top of that, the theme system transforms render arrays
+ * into HTML strings. These two facts combined imply that it is impossible for
+ * #post_render_cache callbacks to bubble up to the root of the render array.
+ * Therefor, drupal_render_collect_post_render_cache() must be called *before*
+ * #theme callbacks, so that it has a chance to examine the full render array.
+ * In short: in order to examine the full render array for #post_render_cache
+ * callbacks, it must use post-order tree traversal, whereas drupal_render()
+ * itself uses pre-order tree traversal.
+ *
+ * @param array &$elements
+ *   The element to collect #post_render_cache callbacks for.
  * @param array $callbacks
  *   Internal use only. The #post_render_callbacks array so far.
  * @param bool $is_root_element
@@ -4261,18 +4272,37 @@ function _drupal_render_process_post_render_cache(array &$elements) {
  * @see drupal_render()
  * @see _drupal_render_process_post_render_cache()
  */
-function drupal_render_collect_post_render_cache(array $elements, array $callbacks = array(), $is_root_element = TRUE) {
-  // Collect all #post_render_cache for this element.
+function drupal_render_collect_post_render_cache(array &$elements, array $callbacks = array(), $is_root_element = TRUE) {
+  // Try to fetch the prerendered element from cache, to determine
+  // #post_render_cache callbacks for this element and all its children. If we
+  // don't do this, then the #post_render_cache tokens will be re-generated, but
+  // they would no longer match the tokens in the render cached markup, causing
+  // the render cache placeholder markup to be sent to the end user!
+  $retrieved_from_cache = FALSE;
+  if (!$is_root_element && isset($elements['#cache'])) {
+    $cached_element = drupal_render_cache_get($elements);
+    if ($cached_element !== FALSE && isset($cached_element['#post_render_cache'])) {
+      $elements['#post_render_cache'] = $cached_element['#post_render_cache'];
+      $retrieved_from_cache = TRUE;
+    }
+  }
+
+  // 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']);
   }
 
-  // Child elements that have #cache set will already have collected all their
-  // children's #post_render_cache callbacks, so no need to traverse further.
-  if (!$is_root_element && isset($elements['#cache'])) {
-    return $callbacks;
-  }
-  else if ($children = element_children($elements)) {
+  // Collect the #post_render_cache callbacks for all child elements, unless
+  // we've already collected them above by retrieving this element (and its
+  // children) from the render cache.
+  if (!$retrieved_from_cache && $children = element_children($elements)) {
     foreach ($children as $child) {
       $callbacks = drupal_render_collect_post_render_cache($elements[$child], $callbacks, FALSE);
     }
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 69bb693..f5ccd9a 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -142,7 +142,7 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
 
     // Cache the rendered output if permitted by the view mode and global entity
     // type configuration.
-    if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && !isset($entity->in_preview) && $this->entityType->isRenderCacheable()) {
+    if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
       $return['#cache'] = array(
         'keys' => array('entity_view', $this->entityTypeId, $entity->id(), $view_mode),
         'granularity' => DRUPAL_CACHE_PER_ROLE,
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 01c51da..9477ed2 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -19,6 +19,7 @@
 use Drupal\field\FieldInterface;
 use Drupal\file\FileInterface;
 use Drupal\user\EntityOwnerInterface;
+use Drupal\node\NodeInterface;
 
 /**
  * Comments are displayed in a flat list - expanded.
@@ -272,7 +273,6 @@ function comment_field_instance_create(FieldInstanceInterface $instance) {
  */
 function comment_field_instance_update(FieldInstanceInterface $instance) {
   if ($instance->getType() == 'comment') {
-    \Drupal::entityManager()->getViewBuilder($instance->entity_type)->resetCache();
     // Comment field settings also affects the rendering of *comment* entities,
     // not only the *commented* entities.
     \Drupal::entityManager()->getViewBuilder('comment')->resetCache();
@@ -426,51 +426,48 @@ function comment_entity_view_alter(&$build, EntityInterface $entity, EntityViewD
 }
 
 /**
- * Implements hook_entity_view().
+ * Implements hook_node_links_alter().
  */
-function comment_entity_view(EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode) {
-  if ($entity->getEntityTypeId() != 'node') {
-    // Comment links are only added to node entity type for backwards
-    // compatibility. Should you require comment links for other entity types
-    // you can do-so by implementing a new field formatter.
-    // @todo Make this configurable from the formatter see
-    //   http://drupal.org/node/1901110
-    return;
-  }
+function comment_node_links_alter(array &$node_links, NodeInterface $node, array &$context) {
+  // Comment links are only added to node entity type for backwards
+  // compatibility. Should you require comment links for other entity types you
+  // can do so by implementing a new field formatter.
+  // @todo Make this configurable from the formatter see
+  //   http://drupal.org/node/1901110
   $fields = \Drupal::service('comment.manager')->getFields('node');
   foreach ($fields as $field_name => $detail) {
-    // Skip fields that entity does not have.
-    if (!$entity->hasField($field_name)) {
+    // Skip fields that the node does not have.
+    if (!$node->hasField($field_name)) {
       continue;
     }
     $links = array();
-    $commenting_status = $entity->get($field_name)->status;
+    $commenting_status = $node->get($field_name)->status;
     if ($commenting_status) {
-      $instance = \Drupal::service('field.info')->getInstance('node', $entity->bundle(), $field_name);
+      $instance = \Drupal::service('field.info')->getInstance('node', $node->bundle(), $field_name);
       // Entity have commenting open or close.
-      if ($view_mode == 'rss') {
+      if ($context['view_mode'] == 'rss') {
         // Add a comments RSS element which is a URL to the comments of this node.
         $options = array(
           'fragment' => 'comments',
           'absolute' => TRUE,
         );
-        $entity->rss_elements[] = array(
+        $node->rss_elements[] = array(
           'key' => 'comments',
-          'value' => $entity->url('canonical', $options),
+          'value' => $node->url('canonical', $options),
         );
       }
-      elseif ($view_mode == 'teaser') {
+      elseif ($context['view_mode'] == 'teaser') {
         // Teaser view: display the number of comments that have been posted,
         // or a link to add new comments if the user has permission, the node
         // is open to new comments, and there currently are none.
         if (user_access('access comments')) {
-          if (!empty($entity->get($field_name)->comment_count)) {
+          if (!empty($node->get($field_name)->comment_count)) {
             $links['comment-comments'] = array(
-              'title' => format_plural($entity->get($field_name)->comment_count, '1 comment', '@count comments'),
+              'title' => format_plural($node->get($field_name)->comment_count, '1 comment', '@count comments'),
               'attributes' => array('title' => t('Jump to the first comment of this posting.')),
               'fragment' => 'comments',
               'html' => TRUE,
-            ) + $entity->urlInfo();
+            ) + $node->urlInfo();
             if (\Drupal::moduleHandler()->moduleExists('history')) {
               $links['comment-new-comments'] = array(
                 'title' => '',
@@ -478,7 +475,7 @@ function comment_entity_view(EntityInterface $entity, EntityViewDisplayInterface
                 'attributes' => array(
                   'class' => 'hidden',
                   'title' => t('Jump to the first new comment of this posting.'),
-                  'data-history-node-last-comment-timestamp' => $entity->get($field_name)->last_comment_timestamp,
+                  'data-history-node-last-comment-timestamp' => $node->get($field_name)->last_comment_timestamp,
                   'data-history-node-field-name' => $field_name,
                 ),
                 'html' => TRUE,
@@ -498,24 +495,24 @@ function comment_entity_view(EntityInterface $entity, EntityViewDisplayInterface
             if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) {
               $links['comment-add']['route_name'] = 'comment.reply';
               $links['comment-add']['route_parameters'] = array(
-                'entity_type' => $entity->getEntityTypeId(),
-                'entity_id' => $entity->id(),
+                'entity_type' => $node->getEntityTypeId(),
+                'entity_id' => $node->id(),
                 'field_name' => $field_name,
               );
             }
             else {
-              $links['comment-add'] += $entity->urlInfo();
+              $links['comment-add'] += $node->urlInfo();
             }
           }
           else {
             $links['comment-forbidden'] = array(
-              'title' => \Drupal::service('comment.manager')->forbiddenMessage($entity, $field_name),
+              'title' => \Drupal::service('comment.manager')->forbiddenMessage($node, $field_name),
               'html' => TRUE,
             );
           }
         }
       }
-      elseif ($view_mode != 'search_index' && $view_mode != 'search_result') {
+      elseif ($context['view_mode'] != 'search_index' && $context['view_mode'] != 'search_result') {
         // Entity in other view modes: add a "post comment" link if the user is
         // allowed to post comments and if this entity is allowing new comments.
         // But we don't want this link if we're building the entity for search
@@ -525,7 +522,7 @@ function comment_entity_view(EntityInterface $entity, EntityViewDisplayInterface
           if (user_access('post comments')) {
             // Show the "post comment" link if the form is on another page, or
             // if there are existing comments that the link will skip past.
-            if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE || (!empty($entity->get($field_name)->comment_count) && user_access('access comments'))) {
+            if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE || (!empty($node->get($field_name)->comment_count) && user_access('access comments'))) {
               $links['comment-add'] = array(
                 'title' => t('Add new comment'),
                 'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')),
@@ -534,19 +531,19 @@ function comment_entity_view(EntityInterface $entity, EntityViewDisplayInterface
               if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) {
                 $links['comment-add']['route_name'] = 'comment.reply';
                 $links['comment-add']['route_parameters'] = array(
-                  'entity_type' => $entity->getEntityTypeId(),
-                  'entity_id' => $entity->id(),
+                  'entity_type' => $node->getEntityTypeId(),
+                  'entity_id' => $node->id(),
                   'field_name' => $field_name,
                 );
               }
               else {
-                $links['comment-add'] += $entity->urlInfo();
+                $links['comment-add'] += $node->urlInfo();
               }
             }
           }
           else {
             $links['comment-forbidden'] = array(
-              'title' => \Drupal::service('comment.manager')->forbiddenMessage($entity, $field_name),
+              'title' => \Drupal::service('comment.manager')->forbiddenMessage($node, $field_name),
               'html' => TRUE,
             );
           }
@@ -554,21 +551,24 @@ function comment_entity_view(EntityInterface $entity, EntityViewDisplayInterface
       }
     }
 
-    $entity->content['links']['comment__' . $field_name] = array(
-      '#theme' => 'links__entity__comment__' . $field_name,
-      '#links' => $links,
-      '#attributes' => array('class' => array('links', 'inline')),
-    );
-    if ($view_mode == 'teaser' && \Drupal::moduleHandler()->moduleExists('history') && \Drupal::currentUser()->isAuthenticated()) {
-      $entity->content['links']['#attached']['library'][] = array('comment', 'drupal.node-new-comments-link');
-
-      // Embed the metadata for the "X new comments" link (if any) on this node.
-      $entity->content['links']['#post_render_cache']['history_attach_timestamp'] = array(
-        array('node_id' => $entity->id()),
-      );
-      $entity->content['links']['#post_render_cache']['Drupal\comment\CommentViewBuilder::attachNewCommentsLinkMetadata'] = array(
-        array('entity_type' => $entity->getEntityTypeId(), 'entity_id' => $entity->id(), 'field_name' => $field_name),
+    if (!empty($links)) {
+      $node_links['comment__' . $field_name] = array(
+        '#theme' => 'links__entity__comment__' . $field_name,
+        '#links' => $links,
+        '#attributes' => array('class' => array('links', 'inline')),
       );
+
+      if ($context['view_mode'] == 'teaser' && \Drupal::moduleHandler()->moduleExists('history') && \Drupal::currentUser()->isAuthenticated()) {
+        $node_links['comment__' . $field_name]['#attached']['library'][] = array('comment', 'drupal.node-new-comments-link');
+
+        // Embed the metadata for the "X new comments" link (if any) on this node.
+        $node_links['comment__' . $field_name]['#post_render_cache']['history_attach_timestamp'] = array(
+          array('node_id' => $node->id()),
+        );
+        $node_links['comment__' . $field_name]['#post_render_cache']['Drupal\comment\CommentViewBuilder::attachNewCommentsLinkMetadata'] = array(
+          array('entity_type' => $node->getEntityTypeId(), 'entity_id' => $node->id(), 'field_name' => $field_name),
+        );
+      }
     }
   }
 }
diff --git a/core/modules/comment/lib/Drupal/comment/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Entity/Comment.php
index 906e6e7..1bb6f84 100644
--- a/core/modules/comment/lib/Drupal/comment/Entity/Comment.php
+++ b/core/modules/comment/lib/Drupal/comment/Entity/Comment.php
@@ -37,7 +37,6 @@
  *   uri_callback = "comment_uri",
  *   fieldable = TRUE,
  *   translatable = TRUE,
- *   render_cache = FALSE,
  *   entity_keys = {
  *     "id" = "cid",
  *     "bundle" = "field_id",
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 0aaffb5..525797c 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -654,7 +654,7 @@ function theme_field($variables) {
   // Render the items.
   $output .= '<div class="field-items"' . $variables['content_attributes'] . '>';
   foreach ($variables['items'] as $delta => $item) {
-    $output .= '<div class="field-item"' . $variables['item_attributes'][$delta] . '>' . drupal_render($item) . '</div>';
+    $output .= '<div class="field-item"' . $variables['item_attributes'][$delta] . '>' . drupal_render($item, TRUE) . '</div>';
   }
   $output .= '</div>';
 
diff --git a/core/modules/node/lib/Drupal/node/Entity/Node.php b/core/modules/node/lib/Drupal/node/Entity/Node.php
index 017e89c..2567c36 100644
--- a/core/modules/node/lib/Drupal/node/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Entity/Node.php
@@ -41,7 +41,6 @@
  *   uri_callback = "node_uri",
  *   fieldable = TRUE,
  *   translatable = TRUE,
- *   render_cache = FALSE,
  *   entity_keys = {
  *     "id" = "nid",
  *     "revision" = "vid",
diff --git a/core/modules/node/lib/Drupal/node/Entity/NodeType.php b/core/modules/node/lib/Drupal/node/Entity/NodeType.php
index 2b36eb5..b1cb917 100644
--- a/core/modules/node/lib/Drupal/node/Entity/NodeType.php
+++ b/core/modules/node/lib/Drupal/node/Entity/NodeType.php
@@ -197,6 +197,9 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $
     else {
       // Invalidate the cache tag of the updated node type only.
       Cache::invalidateTags(array('node_type' => $this->id()));
+
+      // Invalidate the render cache for all nodes.
+      \Drupal::entityManager()->getViewBuilder('node')->resetCache();
     }
   }
 
diff --git a/core/modules/node/lib/Drupal/node/NodeViewBuilder.php b/core/modules/node/lib/Drupal/node/NodeViewBuilder.php
index 75eb28b..e93c864 100644
--- a/core/modules/node/lib/Drupal/node/NodeViewBuilder.php
+++ b/core/modules/node/lib/Drupal/node/NodeViewBuilder.php
@@ -59,6 +59,20 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
   }
 
   /**
+   * {@inheritdoc}
+   */
+  protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
+    $defaults = parent::getBuildDefaults($entity, $view_mode, $langcode);
+
+    // Don't cache nodes that are in 'preview' mode.
+    if (isset($defaults['#cache']) && isset($entity->in_preview)) {
+      unset($defaults['#cache']);
+    }
+
+    return $defaults;
+  }
+
+  /**
    * #post_render_cache callback; replaces the placeholder with node links.
    *
    * Renders the links on a node.
diff --git a/core/modules/node/lib/Drupal/node/Tests/Views/RowPluginTest.php b/core/modules/node/lib/Drupal/node/Tests/Views/RowPluginTest.php
index e2b4f45..33b8ba7 100644
--- a/core/modules/node/lib/Drupal/node/Tests/Views/RowPluginTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/Views/RowPluginTest.php
@@ -134,6 +134,7 @@ public function testRowPlugin() {
 
     // Test with links disabled.
     $view->rowPlugin->options['links'] = FALSE;
+    \Drupal::entityManager()->getViewBuilder('node')->resetCache();
     $output = $view->preview();
     $output = drupal_render($output);
     $this->drupalSetContent($output);
@@ -143,6 +144,7 @@ public function testRowPlugin() {
 
     // Test with links enabled.
     $view->rowPlugin->options['links'] = TRUE;
+    \Drupal::entityManager()->getViewBuilder('node')->resetCache();
     $output = $view->preview();
     $output = drupal_render($output);
     $this->drupalSetContent($output);
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 750250d..53f38d7 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
@@ -781,18 +781,26 @@ 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];
     $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET'));
     $cached_element = cache()->get(drupal_render_cid_create($element))->data;
-    // Parse unique token out of the markup.
+    // Parse unique token out of the cached markup.
     $dom = filter_dom_load($cached_element['#markup']);
     $xpath = new \DOMXPath($dom);
     $nodes = $xpath->query('//*[@token]');
-    $token = $nodes->item(0)->getAttribute('token');
+    $this->assertTrue($nodes->length > 0, 'The token attribute was found in the cached markup');
+    $token = '';
+    if ($nodes->length > 0) {
+      $token = $nodes->item(0)->getAttribute('token');
+    }
+    $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="'. $token . '" /></foo>',
+      '#markup' => '<foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" context="bar:' . $context['bar'] .';" token="'. $expected_token . '" /></foo>',
       '#post_render_cache' => array(
         'common_test_post_render_cache_placeholder' => array(
-          $token => $context,
+          $expected_token => $context,
         ),
       ),
     );
@@ -813,6 +821,152 @@ function testDrupalRenderRenderCachePlaceholder() {
     \Drupal::request()->setMethod($request_method);
   }
 
+  /**
+   * Tests post-render cache-integrated 'render_cache_placeholder' child
+   * element.
+   */
+  function testDrupalRenderChildElementRenderCachePlaceholder() {
+    $context = array('bar' => $this->randomString());
+    $container = array(
+      '#type' => 'container',
+    );
+    $test_element = array(
+      '#type' => 'render_cache_placeholder',
+      '#context' => $context,
+      '#callback' => 'common_test_post_render_cache_placeholder',
+      '#prefix' => '<foo>',
+      '#suffix' => '</foo>'
+    );
+    $container['test_element'] = $test_element;
+    $expected_output = '<div><foo><bar>' . $context['bar'] . '</bar></foo></div>';
+
+    // #cache disabled.
+    drupal_static_reset('_drupal_add_js');
+    $element = $container;
+    $output = drupal_render($element);
+    $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output');
+    $settings = $this->parseDrupalSettings(drupal_get_js());
+    $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.');
+
+    // The cache system is turned off for POST requests.
+    $request_method = \Drupal::request()->getMethod();
+    \Drupal::request()->setMethod('GET');
+
+    // GET request: #cache enabled, cache miss.
+    drupal_static_reset('_drupal_add_js');
+    $element = $container;
+    $element['#cache'] = array('cid' => 'render_cache_placeholder_test_GET');
+    $element['test_element']['#cache'] = array('cid' => 'render_cache_placeholder_test_child_GET');
+    // Simulate element rendering in a template, where sub-items of a renderable
+    // can be sent to drupal_render() before the parent.
+    $child = &$element['test_element'];
+    $element['#children'] = drupal_render($child, TRUE);
+    // Eventually, drupal_render() gets called on the root element.
+    $output = drupal_render($element);
+    $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output');
+    $this->assertTrue(isset($element['#printed']), 'No cache hit');
+    $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.');
+    $settings = $this->parseDrupalSettings(drupal_get_js());
+    $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];
+    $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET'));
+    $cached_element = cache()->get(drupal_render_cid_create($element))->data;
+    // Parse unique token out of the cached markup.
+    $dom = filter_dom_load($cached_element['#markup']);
+    $xpath = new \DOMXPath($dom);
+    $nodes = $xpath->query('//*[@token]');
+    $this->assertTrue($nodes->length > 0, 'The token attribute was found in the cached child element markup');
+    $token = '';
+    if ($nodes->length > 0) {
+      $token = $nodes->item(0)->getAttribute('token');
+    }
+    $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>',
+      '#post_render_cache' => array(
+        'common_test_post_render_cache_placeholder' => array(
+          $expected_token => $context,
+        ),
+      ),
+    );
+    $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached for the child element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
+
+    // GET request: validate cached data (for the parent/entire render array).
+    $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET'));
+    $cached_element = cache()->get(drupal_render_cid_create($element))->data;
+    // Parse unique token out of the cached markup.
+    $dom = filter_dom_load($cached_element['#markup']);
+    $xpath = new \DOMXPath($dom);
+    $nodes = $xpath->query('//*[@token]');
+    $this->assertTrue($nodes->length > 0, 'The token attribute was found in the cached parent element markup');
+    $token = '';
+    if ($nodes->length > 0) {
+      $token = $nodes->item(0)->getAttribute('token');
+    }
+    $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>',
+      '#post_render_cache' => array(
+        'common_test_post_render_cache_placeholder' => array(
+          $expected_token => $context,
+        ),
+      ),
+    );
+    $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached for the parent element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
+
+    // GET request: validate cached data.
+    // Check the cache of the child element again after the parent has been
+    // rendered.
+    $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET'));
+    $cached_element = cache()->get(drupal_render_cid_create($element))->data;
+    // Verify that the child element contains the correct
+    // render_cache_placeholder markup.
+    $expected_token = $child_tokens[0];
+    $dom = filter_dom_load($cached_element['#markup']);
+    $xpath = new \DOMXPath($dom);
+    $nodes = $xpath->query('//*[@token]');
+    $this->assertTrue($nodes->length > 0, 'The token attribute was found in the cached child element markup');
+    $token = '';
+    if ($nodes->length > 0) {
+      $token = $nodes->item(0)->getAttribute('token');
+    }
+    $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>',
+      '#post_render_cache' => array(
+        'common_test_post_render_cache_placeholder' => array(
+          $expected_token => $context,
+        ),
+      ),
+    );
+    $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached for the child element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
+
+    // GET request: #cache enabled, cache hit.
+    drupal_static_reset('_drupal_add_js');
+    $element = $container;
+    $element['#cache'] = array('cid' => 'render_cache_placeholder_test_GET');
+    // Simulate element rendering in a template, where sub-items of a renderable
+    // can be sent to drupal_render before the parent.
+    $child = &$element['test_element'];
+    $element['#children'] = drupal_render($child, TRUE);
+    $output = drupal_render($element);
+    $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output');
+    $this->assertFalse(isset($element['#printed']), 'Cache hit');
+    $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.');
+    $settings = $this->parseDrupalSettings(drupal_get_js());
+    $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.');
+
+    // Restore the previous request method.
+    \Drupal::request()->setMethod($request_method);
+  }
+
   protected function parseDrupalSettings($html) {
     $startToken = 'drupalSettings = ';
     $endToken = '}';
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php b/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php
index f00b5ba..4854c63 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php
@@ -65,6 +65,7 @@ function testTimeZoneHandling() {
 
     // Set time zone to Los Angeles time.
     $config->set('timezone.default', 'America/Los_Angeles')->save();
+    \Drupal::entityManager()->getViewBuilder('node')->resetCache(array($node1, $node2));
 
     // Confirm date format and time zone.
     $this->drupalGet('node/' . $node1->id());
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 20e229d..69d9766 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -611,7 +611,6 @@ function system_element_info() {
   $types['render_cache_placeholder'] = array(
     '#callback' => '',
     '#context' => array(),
-    '#pre_render' => array('drupal_pre_render_render_cache_placeholder'),
   );
 
   return $types;
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php b/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php
index 26f4dc9..5ed7462 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php
@@ -118,6 +118,7 @@ function testPictureOnNodeComment() {
       ->set('features.node_user_picture', FALSE)
       ->set('features.comment_user_picture', FALSE)
       ->save();
+    \Drupal::entityManager()->getViewBuilder('comment')->resetCache();
 
     $this->drupalGet('node/' . $node->id());
     $this->assertNoRaw(file_uri_target($file->getFileUri()), 'User picture not found on node and comment.');
