From b2303acfd6c32ceed33880205cc0c40eb84bac9a Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org> Date: Thu, 6 Aug 2015 11:05:23 +0100 Subject: [PATCH] Issue #2543332 by Wim Leers, effulgentsia, Fabianx, Crell, dawehner, borisson_: Auto-placeholdering for #lazy_builder without bubbling --- core/core.services.yml | 4 + core/lib/Drupal/Core/Render/Renderer.php | 38 +++ core/modules/comment/src/CommentForm.php | 19 +- .../CommentDefaultFormatter.php | 41 ++-- .../CommentDefaultFormatterCacheTagsTest.php | 10 +- .../src/Tests/CommentTranslationUITest.php | 3 +- .../node/src/Tests/NodeTranslationUITest.php | 3 +- .../Core/Render/RendererPlaceholdersTest.php | 227 +++++++++++++++--- .../Tests/Core/Render/RendererTestBase.php | 5 + sites/default/default.services.yml | 31 +++ 10 files changed, 308 insertions(+), 73 deletions(-) diff --git a/core/core.services.yml b/core/core.services.yml index 4eb462cbeccd..ea1a56b40365 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -3,6 +3,10 @@ parameters: twig.config: {} renderer.config: required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions'] + auto_placeholder_conditions: + max-age: 0 + contexts: ['session', 'user'] + tags: [] factory.keyvalue: default: keyvalue.database factory.keyvalue.expirable: diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index cbd9ca853d98..52a849c5ea4b 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -170,6 +170,9 @@ protected function renderPlaceholder($placeholder, array $elements) { // Get the render array for the given placeholder $placeholder_elements = $elements['#attached']['placeholders'][$placeholder]; + // Prevent the render array from being auto-placeholdered again. + $placeholder_elements['#create_placeholder'] = FALSE; + // Render the placeholder into markup. $markup = $this->renderPlain($placeholder_elements); @@ -337,6 +340,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) { throw new \DomainException(sprintf('When a #lazy_builder callback is specified, no properties can exist; all properties must be generated by the #lazy_builder callback. You specified the following properties: %s.', implode(', ', $unsupported_keys))); } } + // Determine whether to do auto-placeholdering. + if (isset($elements['#lazy_builder']) && (!isset($elements['#create_placeholder']) || $elements['#create_placeholder'] !== FALSE) && $this->shouldAutomaticallyPlaceholder($elements)) { + $elements['#create_placeholder'] = TRUE; + } // If instructed to create a placeholder, and a #lazy_builder callback is // present (without such a callback, it would be impossible to replace the // placeholder), replace the current element with a placeholder. @@ -635,6 +642,37 @@ protected function replacePlaceholders(array &$elements) { return TRUE; } + /** + * Whether the given render array should be automatically placeholdered. + * + * @param array $element + * The render array whose cacheability to analyze. + * + * @return bool + * Whether the given render array's cacheability meets the placeholdering + * conditions. + */ + protected function shouldAutomaticallyPlaceholder(array $element) { + $conditions = $this->rendererConfig['auto_placeholder_conditions']; + + // Auto-placeholder if max-age is at or below the configured threshold. + if (isset($element['#cache']['max-age']) && $element['#cache']['max-age'] !== Cache::PERMANENT && $element['#cache']['max-age'] <= $conditions['max-age']) { + return TRUE; + } + + // Auto-placeholder if a high-cardinality cache context is set. + if (isset($element['#cache']['contexts']) && array_intersect($element['#cache']['contexts'], $conditions['contexts'])) { + return TRUE; + } + + // Auto-placeholder if a high-invalidation frequency cache tag is set. + if (isset($element['#cache']['tags']) && array_intersect($element['#cache']['tags'], $conditions['tags'])) { + return TRUE; + } + + return FALSE; + } + /** * Turns this element into a placeholder. * diff --git a/core/modules/comment/src/CommentForm.php b/core/modules/comment/src/CommentForm.php index 7dcb4c807ded..c96202bd5d08 100644 --- a/core/modules/comment/src/CommentForm.php +++ b/core/modules/comment/src/CommentForm.php @@ -78,6 +78,16 @@ public function form(array $form, FormStateInterface $form_state) { $field_definition = $this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$comment->getFieldName()]; $config = $this->config('user.settings'); + // In several places within this function, we vary $form on: + // - The current user's permissions. + // - Whether the current user is authenticated or anonymous. + // - The 'user.settings' configuration. + // - The comment field's definition. + $form['#cache']['contexts'][] = 'user.permissions'; + $form['#cache']['contexts'][] = 'user.roles:authenticated'; + $this->renderer->addCacheableDependency($form, $config); + $this->renderer->addCacheableDependency($form, $field_definition->getConfig($entity->bundle())); + // Use #comment-form as unique jump target, regardless of entity type. $form['#id'] = Html::getUniqueId('comment_form'); $form['#theme'] = array('comment_form__' . $entity->getEntityTypeId() . '__' . $entity->bundle() . '__' . $field_name, 'comment_form'); @@ -90,10 +100,6 @@ public function form(array $form, FormStateInterface $form_state) { $form['#attributes']['data-user-info-from-browser'] = TRUE; } - // Vary per role, because we check a permission above and attach an asset - // library only for authenticated users. - $form['#cache']['contexts'][] = 'user.roles'; - // If not replying to a comment, use our dedicated page callback for new // Comments on entities. if (!$comment->id() && !$comment->hasParentComment()) { @@ -164,6 +170,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['author']['name']['#value'] = $form['author']['name']['#default_value']; $form['author']['name']['#theme'] = 'username'; $form['author']['name']['#account'] = $this->currentUser; + $form['author']['name']['#cache']['contexts'][] = 'user'; } elseif($this->currentUser->isAnonymous()) { $form['author']['name']['#attributes']['data-drupal-default-value'] = $config->get('anonymous'); @@ -210,10 +217,6 @@ public function form(array $form, FormStateInterface $form_state) { '#access' => $is_admin, ); - $this->renderer->addCacheableDependency($form, $config); - // The form depends on the field definition. - $this->renderer->addCacheableDependency($form, $field_definition->getConfig($entity->bundle())); - return parent::form($form, $form_state, $comment); } diff --git a/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php b/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php index acef6162c263..6d98befca4e9 100644 --- a/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php +++ b/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php @@ -184,30 +184,23 @@ public function viewElements(FieldItemListInterface $items) { // 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. - if ($this->currentUser->isAnonymous()) { - $comment = $this->storage->create(array( - 'entity_type' => $entity->getEntityTypeId(), - 'entity_id' => $entity->id(), - 'field_name' => $field_name, - 'comment_type' => $this->getFieldSetting('comment_type'), - 'pid' => NULL, - )); - $output['comment_form'] = $this->entityFormBuilder->getForm($comment); - } - // All other users need a user-specific form, which would break the - // render cache: hence use a #lazy_builder callback. - else { - $output['comment_form'] = [ - '#lazy_builder' => ['comment.lazy_builders:renderForm', [ - $entity->getEntityTypeId(), - $entity->id(), - $field_name, - $this->getFieldSetting('comment_type'), - ]], - '#create_placeholder' => TRUE, - ]; + $output['comment_form'] = [ + '#lazy_builder' => ['comment.lazy_builders:renderForm', [ + $entity->getEntityTypeId(), + $entity->id(), + $field_name, + $this->getFieldSetting('comment_type'), + ]], + ]; + + // @todo Remove this in https://www.drupal.org/node/2543334. Until + // then, \Drupal\Core\Render\Renderer::hasPoorCacheability() isn't + // integrated with cache context bubbling, so this duplicates the + // contexts added by \Drupal\comment\CommentForm::form(). + $output['comment_form']['#cache']['contexts'][] = 'user.permissions'; + $output['comment_form']['#cache']['contexts'][] = 'user.roles:authenticated'; + if ($this->currentUser->isAuthenticated()) { + $output['comment_form']['#cache']['contexts'][] = 'user'; } } } diff --git a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php index 9c40b0c8dab1..790f84140dd1 100644 --- a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php +++ b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php @@ -70,8 +70,7 @@ public function testCacheTags() { ->getViewBuilder('entity_test') ->view($commented_entity); $renderer->renderRoot($build); - $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($build['#cache']['contexts'])->getCacheTags(); - $expected_cache_tags = Cache::mergeTags($cache_context_tags, [ + $expected_cache_tags = [ 'entity_test_view', 'entity_test:' . $commented_entity->id(), 'comment_list', @@ -80,7 +79,7 @@ public function testCacheTags() { 'config:field.field.entity_test.entity_test.comment', 'config:field.storage.comment.comment_body', 'config:user.settings', - ]); + ]; sort($expected_cache_tags); $this->assertEqual($build['#cache']['tags'], $expected_cache_tags); @@ -113,8 +112,7 @@ public function testCacheTags() { ->getViewBuilder('entity_test') ->view($commented_entity); $renderer->renderRoot($build); - $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($build['#cache']['contexts'])->getCacheTags(); - $expected_cache_tags = Cache::mergeTags($cache_context_tags, [ + $expected_cache_tags = [ 'entity_test_view', 'entity_test:' . $commented_entity->id(), 'comment_list', @@ -128,7 +126,7 @@ public function testCacheTags() { 'config:field.field.entity_test.entity_test.comment', 'config:field.storage.comment.comment_body', 'config:user.settings', - ]); + ]; sort($expected_cache_tags); $this->assertEqual($build['#cache']['tags'], $expected_cache_tags); } diff --git a/core/modules/comment/src/Tests/CommentTranslationUITest.php b/core/modules/comment/src/Tests/CommentTranslationUITest.php index dc1bf9e549ac..f7b508ae39f7 100644 --- a/core/modules/comment/src/Tests/CommentTranslationUITest.php +++ b/core/modules/comment/src/Tests/CommentTranslationUITest.php @@ -38,10 +38,9 @@ class CommentTranslationUITest extends ContentTranslationUITestBase { protected $defaultCacheContexts = [ 'languages:language_interface', 'theme', - 'user.permissions', 'timezone', 'url.query_args.pagers:0', - 'user.roles' + 'user' ]; /** diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php index c392750152b6..5ddd64dc87e6 100644 --- a/core/modules/node/src/Tests/NodeTranslationUITest.php +++ b/core/modules/node/src/Tests/NodeTranslationUITest.php @@ -27,13 +27,12 @@ class NodeTranslationUITest extends ContentTranslationUITestBase { protected $defaultCacheContexts = [ 'languages:language_interface', 'theme', - 'user.permissions', 'route.menu_active_trails:account', 'route.menu_active_trails:footer', 'route.menu_active_trails:main', 'route.menu_active_trails:tools', 'timezone', - 'user.roles' + 'user' ]; /** diff --git a/core/tests/Drupal/Tests/Core/Render/RendererPlaceholdersTest.php b/core/tests/Drupal/Tests/Core/Render/RendererPlaceholdersTest.php index 9e3aaa76d65f..5d43afcd3864 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererPlaceholdersTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererPlaceholdersTest.php @@ -37,13 +37,24 @@ protected function setUp() { * Also, different types: * - A) automatically generated placeholder * - 1) manually triggered (#create_placeholder = TRUE) - * - 2) automatically triggered (based on max-age = 0 in its subtree) + * - 2) automatically triggered (based on max-age = 0 at the top level) + * - 3) automatically triggered (based on high cardinality cache contexts at + * the top level) + * - 4) automatically triggered (based on high-invalidation frequency cache + * tags at the top level) + * - 5) automatically triggered (based on max-age = 0 in its subtree, i.e. + * via bubbling) + * - 6) automatically triggered (based on high cardinality cache contexts in + * its subtree, i.e. via bubbling) + * - 7) automatically triggered (based on high-invalidation frequency cache + * tags in its subtree, i.e. via bubbling) * - B) manually generated placeholder * - * So, in total 2*3 = 6 permutations. + * So, in total 2*5 = 10 permutations. * - * @todo Case A2 is not yet supported by core. So that makes for only 4 - * permutations currently. + * @todo Cases A5, A6 and A7 are not yet supported by core. So that makes for + * only 10 permutations currently, instead of 16. That will be done in + * https://www.drupal.org/node/2543334 * * @return array */ @@ -52,15 +63,11 @@ public function providerPlaceholders() { $generate_placeholder_markup = function($cache_keys = NULL) use ($args) { $token_render_array = [ - '#cache' => [], '#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args], ]; if (is_array($cache_keys)) { $token_render_array['#cache']['keys'] = $cache_keys; } - else { - unset($token_render_array['#cache']); - } $token = hash('sha1', serialize($token_render_array)); return SafeMarkup::format('<drupal-render-placeholder callback="@callback" arguments="@arguments" token="@token"></drupal-render-placeholder>', [ '@callback' => 'Drupal\Tests\Core\Render\PlaceholdersTest::callback', @@ -69,6 +76,11 @@ public function providerPlaceholders() { ]); }; + $extract_placeholder_render_array = function ($placeholder_render_array) { + return array_intersect_key($placeholder_render_array, ['#lazy_builder' => TRUE, '#cache' => TRUE]); + }; + + // Note the presence of '#create_placeholder'. $base_element_a1 = [ '#attached' => [ 'drupalSettings' => [ @@ -83,9 +95,55 @@ public function providerPlaceholders() { '#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args], ], ]; + // Note the absence of '#create_placeholder', presence of max-age=0 at the + // top level. $base_element_a2 = [ - // @todo, see docblock + '#attached' => [ + 'drupalSettings' => [ + 'foo' => 'bar', + ], + ], + 'placeholder' => [ + '#cache' => [ + 'contexts' => [], + 'max-age' => 0, + ], + '#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args], + ], + ]; + // Note the absence of '#create_placeholder', presence of high cardinality + // cache context at the top level. + $base_element_a3 = [ + '#attached' => [ + 'drupalSettings' => [ + 'foo' => 'bar', + ], + ], + 'placeholder' => [ + '#cache' => [ + 'contexts' => ['user'], + ], + '#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args], + ], + ]; + // Note the absence of '#create_placeholder', presence of high-invalidation + // frequency cache tag at the top level. + $base_element_a4 = [ + '#attached' => [ + 'drupalSettings' => [ + 'foo' => 'bar', + ], + ], + 'placeholder' => [ + '#cache' => [ + 'contexts' => [], + 'tags' => ['current-temperature'], + ], + '#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args], + ], ]; + // Note the absence of '#create_placeholder', but the presence of + // '#attached[placeholders]'. $base_element_b = [ '#markup' => $generate_placeholder_markup(), '#attached' => [ @@ -94,7 +152,6 @@ public function providerPlaceholders() { ], 'placeholders' => [ $generate_placeholder_markup() => [ - '#cache' => [], '#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args], ], ], @@ -109,9 +166,11 @@ public function providerPlaceholders() { // - automatically created, but manually triggered (#create_placeholder = TRUE) // - uncacheable $element_without_cache_keys = $base_element_a1; + $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a1['placeholder']); $cases[] = [ $element_without_cache_keys, $args, + $expected_placeholder_render_array, FALSE, [], ]; @@ -121,9 +180,11 @@ public function providerPlaceholders() { // - cacheable $element_with_cache_keys = $base_element_a1; $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys; + $expected_placeholder_render_array['#cache']['keys'] = $keys; $cases[] = [ $element_with_cache_keys, $args, + $expected_placeholder_render_array, $keys, [ '#markup' => '<p>This is a rendered placeholder!</p>', @@ -141,31 +202,148 @@ public function providerPlaceholders() { ]; // Case three: render array that has a placeholder that is: + // - automatically created, and automatically triggered due to max-age=0 + // - uncacheable + $element_without_cache_keys = $base_element_a2; + $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a2['placeholder']); + $cases[] = [ + $element_without_cache_keys, + $args, + $expected_placeholder_render_array, + FALSE, + [], + ]; + + // Case four: render array that has a placeholder that is: + // - automatically created, but automatically triggered due to max-age=0 + // - cacheable + $element_with_cache_keys = $base_element_a2; + $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys; + $expected_placeholder_render_array['#cache']['keys'] = $keys; + $cases[] = [ + $element_with_cache_keys, + $args, + $expected_placeholder_render_array, + FALSE, + [] + ]; + + // Case five: render array that has a placeholder that is: + // - automatically created, and automatically triggered due to high + // cardinality cache contexts + // - uncacheable + $element_without_cache_keys = $base_element_a3; + $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a3['placeholder']); + $cases[] = [ + $element_without_cache_keys, + $args, + $expected_placeholder_render_array, + FALSE, + [], + ]; + + // Case six: render array that has a placeholder that is: + // - automatically created, and automatically triggered due to high + // cardinality cache contexts + // - cacheable + $element_with_cache_keys = $base_element_a3; + $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys; + $expected_placeholder_render_array['#cache']['keys'] = $keys; + // The CID parts here consist of the cache keys plus the 'user' cache + // context, which in this unit test is simply the given cache context token, + // see \Drupal\Tests\Core\Render\RendererTestBase::setUp(). + $cid_parts = array_merge($keys, ['user']); + $cases[] = [ + $element_with_cache_keys, + $args, + $expected_placeholder_render_array, + $cid_parts, + [ + '#markup' => '<p>This is a rendered placeholder!</p>', + '#attached' => [ + 'drupalSettings' => [ + 'dynamic_animal' => $args[0], + ], + ], + '#cache' => [ + 'contexts' => ['user'], + 'tags' => [], + 'max-age' => Cache::PERMANENT, + ], + ], + ]; + + // Case seven: render array that has a placeholder that is: + // - automatically created, and automatically triggered due to high + // invalidation frequency cache tags + // - uncacheable + $element_without_cache_keys = $base_element_a4; + $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a4['placeholder']); + $cases[] = [ + $element_without_cache_keys, + $args, + $expected_placeholder_render_array, + FALSE, + [], + ]; + + // Case eight: render array that has a placeholder that is: + // - automatically created, and automatically triggered due to high + // invalidation frequency cache tags + // - cacheable + $element_with_cache_keys = $base_element_a4; + $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys; + $expected_placeholder_render_array['#cache']['keys'] = $keys; + $cases[] = [ + $element_with_cache_keys, + $args, + $expected_placeholder_render_array, + $keys, + [ + '#markup' => '<p>This is a rendered placeholder!</p>', + '#attached' => [ + 'drupalSettings' => [ + 'dynamic_animal' => $args[0], + ], + ], + '#cache' => [ + 'contexts' => [], + 'tags' => ['current-temperature'], + 'max-age' => Cache::PERMANENT, + ], + ], + ]; + + // Case nine: render array that has a placeholder that is: // - manually created // - uncacheable $x = $base_element_b; + $expected_placeholder_render_array = $x['#attached']['placeholders'][$generate_placeholder_markup()]; unset($x['#attached']['placeholders'][$generate_placeholder_markup()]['#cache']); $cases[] = [ $x, $args, + $expected_placeholder_render_array, FALSE, [], ]; - // Case four: render array that has a placeholder that is: + // Case ten: render array that has a placeholder that is: // - manually created // - cacheable $x = $base_element_b; - $x['#markup'] = $generate_placeholder_markup($keys); + $x['#markup'] = $placeholder_markup = $generate_placeholder_markup($keys); $x['#attached']['placeholders'] = [ - $generate_placeholder_markup($keys) => [ + $placeholder_markup => [ '#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args], '#cache' => ['keys' => $keys], ], ]; + $expected_placeholder_render_array = $x['#attached']['placeholders'][$placeholder_markup]; $cases[] = [ $x, $args, + $expected_placeholder_render_array, $keys, [ '#markup' => '<p>This is a rendered placeholder!</p>', @@ -224,8 +402,8 @@ protected function assertPlaceholderRenderCache($cid_parts, array $expected_data * * @dataProvider providerPlaceholders */ - public function testUncacheableParent($element, $args, $placeholder_cid_keys, array $placeholder_expected_render_cache_array) { - if ($placeholder_cid_keys) { + public function testUncacheableParent($element, $args, array $expected_placeholder_render_array, $placeholder_cid_parts, array $placeholder_expected_render_cache_array) { + if ($placeholder_cid_parts) { $this->setupMemoryCache(); } else { @@ -244,7 +422,7 @@ public function testUncacheableParent($element, $args, $placeholder_cid_keys, ar 'dynamic_animal' => $args[0], ]; $this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the placeholder #lazy_builder callback exist.'); - $this->assertPlaceholderRenderCache($placeholder_cid_keys, $placeholder_expected_render_cache_array); + $this->assertPlaceholderRenderCache($placeholder_cid_parts, $placeholder_expected_render_cache_array); } /** @@ -256,25 +434,12 @@ public function testUncacheableParent($element, $args, $placeholder_cid_keys, ar * * @dataProvider providerPlaceholders */ - public function testCacheableParent($test_element, $args, $placeholder_cid_keys, array $placeholder_expected_render_cache_array) { + public function testCacheableParent($test_element, $args, array $expected_placeholder_render_array, $placeholder_cid_parts, array $placeholder_expected_render_cache_array) { $element = $test_element; $this->setupMemoryCache(); $this->setUpRequest('GET'); - // Generate the expected placeholder render array, so that we can generate - // the expected placeholder markup. - $expected_placeholder_render_array = []; - // When there was a child element that created a placeholder, the Renderer - // automatically initializes #cache[contexts]. - if (Element::children($test_element)) { - $expected_placeholder_render_array['#cache']['contexts'] = []; - } - // When the placeholder itself is cacheable, its cache keys are present. - if ($placeholder_cid_keys) { - $expected_placeholder_render_array['#cache']['keys'] = $placeholder_cid_keys; - } - $expected_placeholder_render_array['#lazy_builder'] = ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args]; $token = hash('sha1', serialize($expected_placeholder_render_array)); $expected_placeholder_markup = '<drupal-render-placeholder callback="Drupal\Tests\Core\Render\PlaceholdersTest::callback" arguments="0=' . $args[0] . '" token="' . $token . '"></drupal-render-placeholder>'; $this->assertSame($expected_placeholder_markup, Html::normalize($expected_placeholder_markup), 'Placeholder unaltered by Html::normalize() which is used by FilterHtmlCorrector.'); @@ -291,7 +456,7 @@ public function testCacheableParent($test_element, $args, $placeholder_cid_keys, 'dynamic_animal' => $args[0], ]; $this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the placeholder #lazy_builder callback exist.'); - $this->assertPlaceholderRenderCache($placeholder_cid_keys, $placeholder_expected_render_cache_array); + $this->assertPlaceholderRenderCache($placeholder_cid_parts, $placeholder_expected_render_cache_array); // GET request: validate cached data. $cached_element = $this->memoryCache->get('placeholder_test_GET')->data; diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php index 9ab89785e929..c536c20c3115 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php @@ -89,6 +89,11 @@ class RendererTestBase extends UnitTestCase { 'languages:language_interface', 'theme', ], + 'auto_placeholder_conditions' => [ + 'max-age' => 0, + 'contexts' => ['session', 'user'], + 'tags' => ['current-temperature'], + ], ]; /** diff --git a/sites/default/default.services.yml b/sites/default/default.services.yml index ecaa7b2b01cf..4ab0662e677b 100644 --- a/sites/default/default.services.yml +++ b/sites/default/default.services.yml @@ -84,6 +84,37 @@ parameters: # # @default ['languages:language_interface', 'theme', 'user.permissions'] required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions'] + # Renderer automatic placeholdering conditions: + # + # Drupal allows portions of the page to be automatically deferred when + # rendering to improve cache performance. That is especially helpful for + # cache contexts that vary widely, such as the active user. On some sites + # those may be different, however, such as sites with only a handful of + # users. If you know what the high-cardinality cache contexts are for your + # site, specify those here. If you're not sure, the defaults are fairly safe + # in general. + # + # For more information about rendering optimizations see + # https://www.drupal.org/developing/api/8/render/arrays/cacheability#optimizing + auto_placeholder_conditions: + # Max-age at or below which caching is not considered worthwhile. + # + # Disable by setting to -1. + # + # @default 0 + max-age: 0 + # Cache contexts with a high cardinality. + # + # Disable by setting to []. + # + # @default ['session', 'user'] + contexts: ['session', 'user'] + # Tags with a high invalidation frequency. + # + # Disable by setting to []. + # + # @default [] + tags: [] factory.keyvalue: {} # Default key/value storage service to use. -- GitLab