.../Core/Entity/Controller/EntityViewController.php | 2 +- .../lib/Drupal/Core/Render/BareHtmlPageRenderer.php | 4 ++-- .../Drupal/Core/Render/MainContent/HtmlRenderer.php | 10 +++++----- core/lib/Drupal/Core/Render/Renderer.php | 21 ++++++++++++++++----- .../Tests/Core/Render/RendererBubblingTest.php | 20 ++++++++++---------- .../tests/Drupal/Tests/Core/Render/RendererTest.php | 20 ++++++++++---------- 6 files changed, 44 insertions(+), 33 deletions(-) diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php index 156e749..9a86ad0 100644 --- a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php +++ b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php @@ -91,7 +91,7 @@ public function view(EntityInterface $_entity, $view_mode = 'full', $langcode = $build = $this->entityManager->getTranslationFromContext($_entity) ->get($label_field) ->view($view_mode); - $page['#title'] = $this->renderer->render($build); + $page['#title'] = $this->renderer->renderPlain($build); } } diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php index bf05a3d..0920eef 100644 --- a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php +++ b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php @@ -59,12 +59,12 @@ public function renderBarePage(array $content, $title, $page_theme_property, arr // \Drupal\Core\Render\MainContent\HtmlRenderer::renderResponse() for more // information about this; the exact same pattern is used there and // explained in detail there. - $this->renderer->render($html['page'], TRUE); + $this->renderer->renderRoot($html['page']); // Add the bare minimum of attachments from the system module and the // current maintenance theme. system_page_attachments($html['page']); - return $this->renderer->render($html); + return $this->renderer->renderPlain($html); } } diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php index 17568d1..67804b2 100644 --- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php @@ -142,14 +142,14 @@ public function renderResponse(array $main_content, Request $request, RouteMatch // and hence may not execute any #post_render_cache_callbacks (because they // might add yet more assets to be attached), and therefore it must be // rendered with drupal_render(), not drupal_render_root(). - $this->renderer->render($html['page'], TRUE); + $this->renderer->renderRoot($html['page']); if (isset($html['page_top'])) { - $this->renderer->render($html['page_top'], TRUE); + $this->renderer->renderRoot($html['page_top']); } if (isset($html['page_bottom'])) { - $this->renderer->render($html['page_bottom'], TRUE); + $this->renderer->renderRoot($html['page_bottom']); } - $content = $this->renderer->render($html); + $content = $this->renderer->renderPlain($html, FALSE); // Expose the cache contexts and cache tags associated with this page in a // X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags header respectively. Also @@ -216,7 +216,7 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte // ::renderResponse(). // @todo Remove this once https://www.drupal.org/node/2359901 lands. if (!empty($main_content)) { - $this->renderer->render($main_content, FALSE); + $this->renderer->renderPlain($main_content, FALSE); $main_content = $this->renderer->getCacheableRenderArray($main_content) + [ '#title' => isset($main_content['#title']) ? $main_content['#title'] : NULL ]; diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 44c79e8..5067d7e 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -112,16 +112,27 @@ public function __construct(ControllerResolverInterface $controller_resolver, Th * {@inheritdoc} */ public function renderRoot(&$elements) { - return $this->render($elements, TRUE); + if (isset(static::$stack)) { + throw new \LogicException('A stray renderRoot() invocation is causing bubbling of attached assets to break.'); + } + static::$stack = new \SplStack(); + $output = $this->render($elements, TRUE); + $this->resetStack(); + + return $output; } /** * {@inheritdoc} + * + * @todo Rename to ::renderInIsolation() */ - public function renderPlain(&$elements) { + public function renderPlain(&$elements, $is_root_call = TRUE) { $current_stack = static::$stack; - $this->resetStack(); - $output = $this->renderRoot($elements); + + static::$stack = new \SplStack(); + $output = $this->render($elements, $is_root_call); + static::$stack = $current_stack; return $output; } @@ -172,7 +183,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) { } if (!isset(static::$stack)) { - static::$stack = new \SplStack(); + throw new \LogicException("Render Stack is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain() / renderRoot() or #pre_render pattern instead."); } static::$stack->push(new BubbleableMetadata()); diff --git a/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php b/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php index 5389b02..8a912b5 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php @@ -65,14 +65,14 @@ public function testBubblingWithoutPreRender() { ]; // Render the element and verify the presence of #attached JavaScript. - $this->renderer->render($element); + $this->renderer->renderRoot($element); $expected_libraries = ['test/parent', 'test/child', 'test/subchild']; $this->assertEquals($element['#attached']['library'], $expected_libraries, 'The element, child and subchild #attached libraries are included.'); // Load the element from cache and verify the presence of the #attached // JavaScript. $element = ['#cache' => ['keys' => ['simpletest', 'drupal_render', 'children_attached']]]; - $this->assertTrue(strlen($this->renderer->render($element)) > 0, 'The element was retrieved from cache.'); + $this->assertTrue(strlen($this->renderer->renderRoot($element)) > 0, 'The element was retrieved from cache.'); $this->assertEquals($element['#attached']['library'], $expected_libraries, 'The element, child and subchild #attached libraries are included.'); } @@ -90,7 +90,7 @@ public function testContextBubblingEdgeCases(array $element, array $expected_top ->method('convertTokensToKeys') ->willReturnArgument(0); - $this->renderer->render($element); + $this->renderer->renderRoot($element); $this->assertEquals($expected_top_level_contexts, $element['#cache']['contexts'], 'Expected cache contexts found.'); foreach ($expected_cache_items as $cid => $expected_cache_item) { @@ -295,7 +295,7 @@ public function testConditionalCacheContextBubblingSelfHealing() { // contexts: user.roles. $element = $test_element; $current_user_role = 'A'; - $this->renderer->render($element); + $this->renderer->renderRoot($element); $this->assertRenderCacheItem('parent', [ '#cache_redirect' => TRUE, '#cache' => [ @@ -319,7 +319,7 @@ public function testConditionalCacheContextBubblingSelfHealing() { // contexts: foo, user.roles. $element = $test_element; $current_user_role = 'B'; - $this->renderer->render($element); + $this->renderer->renderRoot($element); $this->assertRenderCacheItem('parent', [ '#cache_redirect' => TRUE, '#cache' => [ @@ -351,7 +351,7 @@ public function testConditionalCacheContextBubblingSelfHealing() { // and 'user.roles' cache contexts, resulting in a cache miss every time.) $element = $test_element; $current_user_role = 'A'; - $this->renderer->render($element); + $this->renderer->renderRoot($element); $this->assertRenderCacheItem('parent', [ '#cache_redirect' => TRUE, '#cache' => [ @@ -375,7 +375,7 @@ public function testConditionalCacheContextBubblingSelfHealing() { // accessible => bubbled cache contexts: foo, bar, user.roles. $element = $test_element; $current_user_role = 'C'; - $this->renderer->render($element); + $this->renderer->renderRoot($element); $final_parent_cache_item = [ '#cache_redirect' => TRUE, '#cache' => [ @@ -399,7 +399,7 @@ public function testConditionalCacheContextBubblingSelfHealing() { // Request 5: role A again, verifying the merging like we did for request 3. $element = $test_element; $current_user_role = 'A'; - $this->renderer->render($element); + $this->renderer->renderRoot($element); $this->assertRenderCacheItem('parent', $final_parent_cache_item); $this->assertRenderCacheItem('parent:bar:foo:r.A', [ '#attached' => [], @@ -415,7 +415,7 @@ public function testConditionalCacheContextBubblingSelfHealing() { // Request 6: role B again, verifying the merging like we did for request 3. $element = $test_element; $current_user_role = 'B'; - $this->renderer->render($element); + $this->renderer->renderRoot($element); $this->assertRenderCacheItem('parent', $final_parent_cache_item); $this->assertRenderCacheItem('parent:bar:foo:r.B', [ '#attached' => [], @@ -519,7 +519,7 @@ public function testOverWriteCacheKeys() { ], '#pre_render' => [__NAMESPACE__ . '\\BubblingTest::bubblingCacheOverwritePrerender'], ]; - $this->renderer->render($data); + $this->renderer->renderRoot($data); } } diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php index bbea4b9..70a3cdc 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php @@ -44,7 +44,7 @@ public function testRenderBasic($build, $expected, callable $setup_code = NULL) $setup_code(); } - $this->assertSame($expected, $this->renderer->render($build)); + $this->assertSame($expected, $this->renderer->renderRoot($build)); } /** @@ -315,7 +315,7 @@ public function testRenderSorting() { '#markup' => $first, ], ]; - $output = $this->renderer->render($elements); + $output = $this->renderer->renderRoot($elements); // The lowest weight element should appear last in $output. $this->assertTrue(strpos($output, $second) > strpos($output, $first), 'Elements were sorted correctly by weight.'); @@ -350,7 +350,7 @@ public function testRenderSortingWithSetHashSorted() { ), '#sorted' => TRUE, ); - $output = $this->renderer->render($elements); + $output = $this->renderer->renderRoot($elements); // The elements should appear in output in the same order as the array. $this->assertTrue(strpos($output, $second) < strpos($output, $first), 'Elements were not sorted.'); @@ -432,11 +432,11 @@ public function testRenderTwice() { '#markup' => 'test', ]; - $this->assertEquals('test', $this->renderer->render($build)); + $this->assertEquals('test', $this->renderer->renderRoot($build)); $this->assertTrue($build['#printed']); // We don't want to reprint already printed render arrays. - $this->assertEquals('', $this->renderer->render($build)); + $this->assertEquals('', $this->renderer->renderRoot($build)); } /** @@ -463,10 +463,10 @@ protected function assertAccess($build, $access) { $sensitive_content = $this->randomContextValue(); $build['#markup'] = $sensitive_content; if ($access) { - $this->assertSame($sensitive_content, $this->renderer->render($build)); + $this->assertSame($sensitive_content, $this->renderer->renderRoot($build)); } else { - $this->assertSame('', $this->renderer->render($build)); + $this->assertSame('', $this->renderer->renderRoot($build)); } } @@ -558,13 +558,13 @@ public function testRenderCache() { // Render the element and confirm that it goes through the rendering // process (which will set $element['#printed']). $element = $test_element; - $this->renderer->render($element); + $this->renderer->renderRoot($element); $this->assertTrue(isset($element['#printed']), 'No cache hit'); // Render the element again and confirm that it is retrieved from the cache // instead (so $element['#printed'] will not be set). $element = $test_element; - $this->renderer->render($element); + $this->renderer->renderRoot($element); $this->assertFalse(isset($element['#printed']), 'Cache hit'); // Test that cache tags are correctly collected from the render element, @@ -601,7 +601,7 @@ public function testRenderCacheMaxAge($max_age, $is_render_cached, $render_cache ], '#markup' => '', ]; - $this->renderer->render($element); + $this->renderer->renderRoot($element); $cache_item = $this->cacheFactory->get('render')->get('render_cache_test:en:stark'); if (!$is_render_cached) {