Commit fe04699e authored by catch's avatar catch

Issue #2429257 by Wim Leers, Fabianx, effulgentsia: Bubble cache contexts

parent 9dfa950c
......@@ -21,6 +21,26 @@ class Cache {
*/
const PERMANENT = CacheBackendInterface::CACHE_PERMANENT;
/**
* Merges arrays of cache contexts and removes duplicates.
*
* @param string[] …
* Arrays of cache contexts to merge.
*
* @return string[]
* The merged array of cache contexts.
*/
public static function mergeContexts() {
$cache_context_arrays = func_get_args();
$cache_contexts = [];
foreach ($cache_context_arrays as $contexts) {
$cache_contexts = array_merge($cache_contexts, $contexts);
}
$cache_contexts = array_unique($cache_contexts);
sort($cache_contexts);
return $cache_contexts;
}
/**
* Merges arrays of cache tags and removes duplicates.
*
......
......@@ -105,6 +105,7 @@ public function getLabels($include_calculated_cache_contexts = FALSE) {
* @throws \InvalidArgumentException
*/
public function convertTokensToKeys(array $context_tokens) {
sort($context_tokens);
$keys = [];
foreach (static::parseTokens($context_tokens) as $context) {
list($context_id, $parameter) = $context;
......
......@@ -185,9 +185,6 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
'contexts' => array(
'theme',
'user.roles',
// @todo Move this out of here and into field formatters that depend
// on the timezone. Blocked on https://drupal.org/node/2099137.
'timezone',
),
'bin' => $this->cacheBin,
);
......
......@@ -30,6 +30,9 @@ class LanguageFormatter extends FormatterBase {
public function viewElements(FieldItemListInterface $items) {
$elements = array();
// The 'language' cache context is not necessary, because what is printed
// here is the language's name in English, not in the language of the
// response.
foreach ($items as $delta => $item) {
$elements[$delta] = array('#markup' => $item->language ? String::checkPlain($item->language->getName()) : '');
}
......
......@@ -31,7 +31,14 @@ public function viewElements(FieldItemListInterface $items) {
$elements = array();
foreach ($items as $delta => $item) {
$elements[$delta] = array('#markup' => format_date($item->value));
$elements[$delta] = [
'#cache' => [
'contexts' => [
'timezone',
],
],
'#markup' => format_date($item->value)
];
}
return $elements;
......
......@@ -17,6 +17,13 @@
*/
class BubbleableMetadata {
/**
* Cache contexts.
*
* @var string[]
*/
protected $contexts;
/**
* Cache tags.
*
......@@ -41,6 +48,8 @@ class BubbleableMetadata {
/**
* Constructs a BubbleableMetadata value object.
*
* @param string[] $contexts
* An array of cache contexts.
* @param string[] $tags
* An array of cache tags.
* @param array $attached
......@@ -48,7 +57,8 @@ class BubbleableMetadata {
* @param array $post_render_cache
* An array of #post_render_cache metadata.
*/
public function __construct(array $tags = [], array $attached = [], array $post_render_cache = []) {
public function __construct(array $contexts = [], array $tags = [], array $attached = [], array $post_render_cache = []) {
$this->contexts = $contexts;
$this->tags = $tags;
$this->attached = $attached;
$this->postRenderCache = $post_render_cache;
......@@ -69,6 +79,7 @@ public function __construct(array $tags = [], array $attached = [], array $post_
*/
public function merge(BubbleableMetadata $other) {
$result = new BubbleableMetadata();
$result->contexts = Cache::mergeContexts($this->contexts, $other->contexts);
$result->tags = Cache::mergeTags($this->tags, $other->tags);
$result->attached = Renderer::mergeAttachments($this->attached, $other->attached);
$result->postRenderCache = NestedArray::mergeDeep($this->postRenderCache, $other->postRenderCache);
......@@ -82,6 +93,7 @@ public function merge(BubbleableMetadata $other) {
* A render array.
*/
public function applyTo(array &$build) {
$build['#cache']['contexts'] = $this->contexts;
$build['#cache']['tags'] = $this->tags;
$build['#attached'] = $this->attached;
$build['#post_render_cache'] = $this->postRenderCache;
......@@ -97,6 +109,7 @@ public function applyTo(array &$build) {
*/
public static function createFromRenderArray(array $build) {
$meta = new static();
$meta->contexts = (isset($build['#cache']['contexts'])) ? $build['#cache']['contexts'] : [];
$meta->tags = (isset($build['#cache']['tags'])) ? $build['#cache']['tags'] : [];
$meta->attached = (isset($build['#attached'])) ? $build['#attached'] : [];
$meta->postRenderCache = (isset($build['#post_render_cache'])) ? $build['#post_render_cache'] : [];
......
This diff is collapsed.
......@@ -72,13 +72,31 @@ public function renderPlain(&$elements);
* provided by the children is typically inserted into the markup generated by
* the parent array.
*
* An important aspect of rendering is the bubbling of rendering metadata:
* cache tags, attached assets and #post_render_cache metadata all need to be
* bubbled up. That information is needed once the rendering to a HTML string
* is completed: the resulting HTML for the page must know by which cache tags
* it should be invalidated, which (CSS and JavaScript) assets must be loaded,
* and which #post_render_cache callbacks should be executed. A stack data
* structure is used to perform this bubbling.
* An important aspect of rendering is caching the result, when appropriate.
* Because the HTML of a rendered item includes all of the HTML of the
* rendered children, caching it requires certain information to bubble up
* from child elements to their parents:
* - Cache contexts, so that the render cache is varied by every context that
* affects the rendered HTML. Because cache contexts affect the cache ID,
* and therefore must be resolved for cache hits as well as misses, it is
* up to the implementation of this interface to decide how to implement
* the caching of items whose children specify cache contexts not directly
* specified by the parent. \Drupal\Core\Render\Renderer implements this
* with a lazy two-tier caching strategy. Alternate strategies could be to
* not cache such parents at all or to cache them with the child elements
* replaced by placeholder tokens that are dynamically rendered after cache
* retrieval.
* - Cache tags, so that cached renderings are invalidated when site content
* or configuration that can affect that rendering changes.
* - #post_render_cache callbacks, for executing code to handle dynamic
* requirements that cannot be cached.
* A stack of \Drupal\Core\Render\BubbleableMetadata objects can be used to
* perform this bubbling.
*
* Additionally, whether retrieving from cache or not, it is important to
* know all of the assets (CSS and JavaScript) required by the rendered HTML,
* and this must also bubble from child to parent. Therefore,
* \Drupal\Core\Render\BubbleableMetadata includes that data as well.
*
* The process of rendering an element is recursive unless the element defines
* an implemented theme hook in #theme. During each call to
......@@ -111,6 +129,20 @@ public function renderPlain(&$elements);
* metadata from the element retrieved from render cache. Then, this stack
* frame is bubbled: the two topmost frames are popped from the stack,
* they are merged, and the result is pushed back onto the stack.
* However, also in case of a cache miss we have to do something. Note
* that a Renderer renders top-down, which means that we try to render a
* parent first, and we try to avoid the work of rendering the children by
* using the render cache. Though in this case, we are dealing with a
* cache miss. So a Renderer traverses down the tree, rendering all
* children. In doing so, the render stack is updated with the bubbleable
* metadata of the children. That means that once the children are
* rendered, we can render cache this element. But the cache ID may have
* *changed* at that point, because the children's cache contexts have
* been bubbled!
* It is for that case that we must store the current (pre-bubbling) cache
* ID, so that we can compare it with the new (post-bubbling) cache ID
* when writing to the cache. We store the current cache ID in
* $pre_bubbling_cid.
* - If this element has #type defined and the default attributes for this
* element have not already been merged in (#defaults_loaded = TRUE) then
* the defaults for this type of element, defined in hook_element_info(),
......@@ -210,6 +242,12 @@ public function renderPlain(&$elements);
* - If this element has #cache defined, the rendered output of this element
* is saved to Renderer::render()'s internal cache. This includes the
* changes made by #post_render.
* At the same time, if $pre_bubbling_cid is set, it is compared to the
* calculated cache ID. If they are different, then a redirecting cache
* item is created, containing the #cache metadata of the current element,
* and written to cache using the value of $pre_bubbling_cid as the cache
* ID. This ensures the pre-bubbling ("wrong") cache ID redirects to the
* post-bubbling ("right") cache ID.
* - If this element has an array of #post_render_cache functions defined,
* or any of its children has (which we would know thanks to the stack
* having been updated just before the render caching step), they are
......
......@@ -157,6 +157,7 @@ public function viewElements(FieldItemListInterface $items) {
// Unpublished comments are not included in
// $entity->get($field_name)->comment_count, but unpublished comments
// should display if the user is an administrator.
$elements['#cache']['contexts'][] = 'user.roles';
if ($this->currentUser->hasPermission('access comments') || $this->currentUser->hasPermission('administer comments')) {
// This is a listing of Comment entities, so associate its list cache
// tag for correct invalidation.
......@@ -182,6 +183,7 @@ public function viewElements(FieldItemListInterface $items) {
// display below the entity. Do not show the form for the print view mode.
if ($status == CommentItemInterface::OPEN && $comment_settings['form_location'] == CommentItemInterface::FORM_BELOW && $this->viewMode != 'print') {
// Only show the add comment form if the user has permission.
$elements['#cache']['contexts'][] = 'user.roles';
if ($this->currentUser->hasPermission('post comments')) {
// All users in the "anonymous" role can use the same form: it is fine
// for this form to be stored in the render cache.
......
......@@ -127,6 +127,11 @@ public function viewElements(FieldItemListInterface $items) {
// Display the date using theme datetime.
$elements[$delta] = array(
'#cache' => [
'contexts' => [
'timezone',
],
],
'#theme' => 'time',
'#text' => $formatted_date,
'#html' => FALSE,
......
......@@ -46,7 +46,14 @@ public function viewElements(FieldItemListInterface $items) {
}
$output = $date->format($format);
}
$elements[$delta] = array('#markup' => $output);
$elements[$delta] = [
'#cache' => [
'contexts' => [
'timezone',
],
],
'#markup' => $output,
];
}
return $elements;
......
......@@ -85,6 +85,13 @@ class FilterProcessResult {
*/
protected $cacheTags;
/**
* The associated cache contexts.
*
* @var string[]
*/
protected $cacheContexts;
/**
* The associated #post_render_cache callbacks.
*
......@@ -105,6 +112,7 @@ public function __construct($processed_text) {
$this->assets = array();
$this->cacheTags = array();
$this->cacheContexts = array();
$this->postRenderCacheCallbacks = array();
}
......@@ -174,6 +182,41 @@ public function setCacheTags(array $cache_tags) {
return $this;
}
/**
* Gets cache contexts associated with the processed text.
*
* @return string[]
*/
public function getCacheContexts() {
return $this->cacheContexts;
}
/**
* Adds cache contexts associated with the processed text.
*
* @param string[] $cache_contexts
* The cache contexts to be added.
*
* @return $this
*/
public function addCacheContexts(array $cache_contexts) {
$this->cacheContexts = Cache::mergeContexts($this->cacheContexts, $cache_contexts);
return $this;
}
/**
* Sets cache contexts associated with the processed text.
*
* @param string[] $cache_contexts
* The cache contexts to be associated.
*
* @return $this
*/
public function setCacheContexts(array $cache_contexts) {
$this->cacheContexts = $cache_contexts;
return $this;
}
/**
* Gets assets associated with the processed text.
*
......@@ -256,6 +299,7 @@ public function setPostRenderCacheCallbacks(array $post_render_cache_callbacks)
*/
public function getBubbleableMetadata() {
return new BubbleableMetadata(
$this->getCacheContexts(),
$this->getCacheTags(),
$this->getAssets(),
$this->getPostRenderCacheCallbacks()
......
......@@ -216,6 +216,10 @@ function testProcessedTextElement() {
'weight' => 0,
'status' => TRUE,
),
'filter_test_cache_contexts' => array(
'weight' => 0,
'status' => TRUE,
),
'filter_test_post_render_cache' => array(
'weight' => 1,
'status' => TRUE,
......@@ -253,6 +257,11 @@ function testProcessedTextElement() {
'foo:baz',
);
$this->assertEqual($expected_cache_tags, $build['#cache']['tags'], 'Expected cache tags present.');
$expected_cache_contexts = [
// The cache context set by the filter_test_cache_contexts filter.
'language',
];
$this->assertEqual($expected_cache_contexts, $build['#cache']['contexts'], 'Expected cache contexts present.');
$expected_markup = '<p>Hello, world!</p><p>This is a dynamic llama.</p>';
$this->assertEqual($expected_markup, $build['#markup'], 'Expected #post_render_cache callback has been applied.');
}
......
<?php
/**
* @file
* Contains \Drupal\filter_test\Plugin\Filter\FilterTestCacheContexts.
*/
namespace Drupal\filter_test\Plugin\Filter;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
/**
* Provides a test filter to associate cache contexts.
*
* @Filter(
* id = "filter_test_cache_contexts",
* title = @Translation("Testing filter"),
* description = @Translation("Does not change content; associates cache contexts."),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
* )
*/
class FilterTestCacheContexts extends FilterBase {
/**
* {@inheritdoc}
*/
public function process($text, $langcode) {
$result = new FilterProcessResult($text);
// The changes made by this filter are language-specific.
$result->addCacheContexts(['language']);
return $result;
}
}
......@@ -42,6 +42,13 @@ protected function createEntity() {
return $node;
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
return ['timezone'];
}
/**
* {@inheritdoc}
*
......
......@@ -127,6 +127,21 @@ protected static function generateStandardizedInfo($entity_type_label, $group) {
*/
abstract protected function createEntity();
/**
* Returns the additional (non-standard) cache contexts for the tested entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to be tested, as created by createEntity().
*
* @return string[]
* An array of the additional cache contexts.
*
* @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
*/
protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
return [];
}
/**
* Returns the additional (non-standard) cache tags for the tested entity.
*
......@@ -288,6 +303,9 @@ public function testReferencedEntity() {
$empty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_empty', ['entity_type_id' => $entity_type]);
$nonempty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_labels_alphabetically', ['entity_type_id' => $entity_type]);
// The default cache contexts for rendered entities.
$entity_cache_contexts = ['theme', 'user.roles'];
// Cache tags present on every rendered page.
$page_cache_tags = Cache::mergeTags(
['rendered'],
......@@ -337,15 +355,18 @@ public function testReferencedEntity() {
// Verify a cache hit, but also the presence of the correct cache tags.
$this->verifyPageCache($referencing_entity_url, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags));
// Also verify the existence of an entity render cache entry.
$cid = 'entity_view:entity_test:' . $this->referencing_entity->id() . ':full:classy:r.anonymous:' . date_default_timezone_get();
$this->verifyRenderCache($cid, $referencing_entity_cache_tags);
$cache_keys = ['entity_view', 'entity_test', $this->referencing_entity->id(), 'full'];
$cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
$redirected_cid = $this->createRedirectedCacheId($cache_keys, $entity_cache_contexts);
$this->verifyRenderCache($cid, $referencing_entity_cache_tags, $redirected_cid);
$this->pass("Test non-referencing entity.", 'Debug');
$this->verifyPageCache($non_referencing_entity_url, 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$this->verifyPageCache($non_referencing_entity_url, 'HIT', Cache::mergeTags($non_referencing_entity_cache_tags, $page_cache_tags));
// Also verify the existence of an entity render cache entry.
$cid = 'entity_view:entity_test:' . $this->non_referencing_entity->id() . ':full:classy:r.anonymous:' . date_default_timezone_get();
$cache_keys = ['entity_view', 'entity_test', $this->non_referencing_entity->id(), 'full'];
$cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
$this->verifyRenderCache($cid, $non_referencing_entity_cache_tags);
......@@ -578,6 +599,52 @@ public function testReferencedEntity() {
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT', Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->getAdditionalCacheTagsForEntityListing(), $page_cache_tags));
}
/**
* Creates a cache ID from a list of cache keys and a set of cache contexts.
*
* @param string[] $keys
* A list of cache keys.
* @param string[] $contexts
* A set of cache contexts.
*
* @return string
* The cache ID string.
*/
protected function createCacheId(array $keys, array $contexts) {
$cid_parts = $keys;
$contexts = \Drupal::service('cache_contexts')->convertTokensToKeys($contexts);
$cid_parts = array_merge($cid_parts, $contexts);
return implode(':', $cid_parts);
}
/**
* Creates the redirected cache ID, if any.
*
* If a subclass overrides ::getAdditionalCacheContextsForEntity(), it can
* specify the additional cache contexts by which the given entity must be
* varied, because those are the cache contexts that are bubbled from the
* field formatters.
*
* @param string[] $keys
* A list of cache keys used for the regular (pre-bubbling) CID.
* @param string[] $contexts
* A set of cache contexts used for the regular (pre-bubbling) CID.
*
* @return string|null
* The redirected (post-bubbling) CID, if any.
*/
protected function createRedirectedCacheId(array $keys, array $contexts) {
$additional_cache_contexts = $this->getAdditionalCacheContextsForEntity($this->referencing_entity);
if (count($additional_cache_contexts)) {
return $this->createCacheId($keys, Cache::mergeContexts($contexts, $additional_cache_contexts));
}
else {
return NULL;
}
}
/**
* Verify that a given render cache entry exists, with the correct cache tags.
*
......@@ -585,14 +652,33 @@ public function testReferencedEntity() {
* The render cache item ID.
* @param array $tags
* An array of expected cache tags.
* @param string|null $redirected_cid
* (optional) The redirected render cache item ID.
*/
protected function verifyRenderCache($cid, array $tags) {
protected function verifyRenderCache($cid, array $tags, $redirected_cid = NULL) {
// Also verify the existence of an entity render cache entry.
$cache_entry = \Drupal::cache('render')->get($cid);
$this->assertTrue($cache_entry, 'A render cache entry exists.');
sort($cache_entry->tags);
sort($tags);
$this->assertIdentical($cache_entry->tags, $tags);
if ($redirected_cid === NULL) {
$this->assertTrue(!isset($cache_entry->data['#cache_redirect']), 'Render cache entry is not a redirect.');
}
else {
// Verify that $cid contains a cache redirect.
$this->assertTrue(isset($cache_entry->data['#cache_redirect']), 'Render cache entry is a redirect.');
// Verify that the cache redirect points to the expected CID.
$redirect_cache_metadata = $cache_entry->data['#cache'];
$actual_redirection_cid = $this->createCacheId(
$redirect_cache_metadata['keys'],
$redirect_cache_metadata['contexts']
);
$this->assertIdentical($redirected_cid, $actual_redirection_cid);
// Finally, verify that the redirected CID exists and has the same cache
// tags.
$this->verifyRenderCache($redirected_cid, $tags);
}
}
}
......@@ -30,6 +30,9 @@ public function testEntityUri() {
// Selects the view mode that will be used.
$view_mode = $this->selectViewMode($entity_type);
// The default cache contexts for rendered entities.
$entity_cache_contexts = ['theme', 'user.roles'];
// Generate the standardized entity cache tags.
$cache_tag = $this->entity->getCacheTags();
$view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type)->getCacheTags();
......@@ -45,10 +48,11 @@ public function testEntityUri() {
// Also verify the existence of an entity render cache entry, if this entity
// type supports render caching.
if (\Drupal::entityManager()->getDefinition($entity_type)->isRenderCacheable()) {
$cid = 'entity_view:' . $entity_type . ':' . $this->entity->id() . ':' . $view_mode . ':classy:r.anonymous:' . date_default_timezone_get();
$cache_entry = \Drupal::cache('render')->get($cid);
$cache_keys = ['entity_view', $entity_type, $this->entity->id(), $view_mode];
$cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
$redirected_cid = $this->createRedirectedCacheId($cache_keys, $entity_cache_contexts);
$expected_cache_tags = Cache::mergeTags($cache_tag, $view_cache_tag, $this->getAdditionalCacheTagsForEntity($this->entity), array($render_cache_tag));
$this->verifyRenderCache($cid, $expected_cache_tags);
$this->verifyRenderCache($cid, $expected_cache_tags, $redirected_cid);
}
// Verify that after modifying the entity, there is a cache miss.
......
......@@ -34,6 +34,8 @@ class TextDefaultFormatter extends FormatterBase {
public function viewElements(FieldItemListInterface $items) {
$elements = array();
// The ProcessedText element already handles cache context & tag bubbling.
// @see \Drupal\filter\Element\ProcessedText::preRenderText()
foreach ($items as $delta => $item) {
$elements[$delta] = array(
'#type' => 'processed_text',
......
......@@ -81,6 +81,8 @@ public function viewElements(FieldItemListInterface $items) {
$element['#text_summary_trim_length'] = $this->getSetting('trim_length');
};
// The ProcessedText element already handles cache context & tag bubbling.
// @see \Drupal\filter\Element\ProcessedText::preRenderText()
foreach ($items as $delta => $item) {
$elements[$delta] = array(
'#type' => 'processed_text',
......
......@@ -33,9 +33,9 @@ public function testConvertTokensToKeys() {
]);
$expected = [
'bar',
'baz.cnenzrgreN',
'baz.cnenzrgreO',
'bar',
];
$this->assertEquals($expected, $new_keys);
}
......
......@@ -35,11 +35,12 @@ public function providerTestApplyTo() {
$data = [];
$empty_metadata = new BubbleableMetadata();
$nonempty_metadata = new BubbleableMetadata(['foo:bar'], ['settings' => ['foo' => 'bar']]);
$nonempty_metadata = new BubbleableMetadata(['qux'], ['foo:bar'], ['settings' => ['foo' => 'bar']]);
$empty_render_array = [];
$nonempty_render_array = [
'#cache' => [
'contexts' => ['qux'],
'tags' => ['llamas:are:awesome:but:kittens:too'],
],
'#attached' => [
......@@ -53,7 +54,8 @@ public function providerTestApplyTo() {
$expected_when_empty_metadata = [
'#cache' => [
'tags' => []
'contexts' => [],
'tags' => [],
],
'#attached' => [],
'#post_render_cache' => [],
......@@ -61,7 +63,10 @@ public function providerTestApplyTo() {
$data[] = [$empty_metadata, $empty_render_array, $expected_when_empty_metadata];
$data[] = [$empty_metadata, $nonempty_render_array, $expected_when_empty_metadata];
$expected_when_nonempty_metadata = [
'#cache' => ['tags' => ['foo:bar']],
'#cache' => [
'contexts' => ['qux'],
'tags' => ['foo:bar'],
],
'#attached' => [
'settings' => [
'foo' => 'bar',
......@@ -92,11 +97,12 @@ public function providerTestCreateFromRenderArray() {
$data = [];
$empty_metadata = new BubbleableMetadata();
$nonempty_metadata = new BubbleableMetadata(['foo:bar'], ['settings' => ['foo' => 'bar']]);
$nonempty_metadata = new BubbleableMetadata(['qux'], ['foo:bar'], ['settings' => ['foo' => 'bar']]);
$empty_render_array = [];
$nonempty_render_array = [
'#cache' => [
'contexts' => ['qux'],
'tags' => ['foo:bar'],
],
'#attached' => [
......
......@@ -89,7 +89,10 @@ public function testPostRenderCacheWithColdCache() {
'#markup' => '<p>#cache enabled, GET</p>',
'#attached' => $test_element['#attached'],
'#post_render_cache' => $test_element['#post_render_cache'],
'#cache' => ['tags' => ['rendered']],
'#cache' => [
'contexts' => [],
'tags' => ['rendered'],
],
];
$this->assertSame($cached_element, $expected_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
......@@ -222,7 +225,10 @@ public function testRenderChildrenPostRenderCacheDifferentContexts() {
$context_3,
]
],
'#cache' => ['tags' => ['rendered']],
'#cache' => [
'contexts' => [],
'tags' => ['rendered'],
],
];
$dom = Html::load($cached_element['#markup']);
......@@ -314,7 +320,10 @@ public function testRenderChildrenPostRenderCacheComplex() {
$context_3,
]
],
'#cache' => ['tags' => ['rendered']],
'#cache' => [
'contexts' => [],
'tags' => ['rendered'],
],
];
$dom = Html::load($cached_parent_element['#markup']);
......@@ -337,7 +346,10 @@ public function testRenderChildrenPostRenderCacheComplex() {
$context_3,
]
],
'#cache' => ['tags' => ['rendered']],
'#cache' => [
'contexts' => [],
'tags' => ['rendered'],
],
];
$dom = Html::load($cached_child_element['#markup']);
......@@ -448,7 +460,10 @@ public function testPlaceholder() {
$context
],
],
'#cache' => ['tags' => ['rendered']],
'#cache' => [
'contexts' => [],
'tags' => ['rendered'],
],
];
$this->assertSame($cached_element, $expected_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
......@@ -543,7 +558,10 @@ public function testChildElementPlaceholder() {
$context,