Commit 0549d593 authored by alexpott's avatar alexpott

Issue #2551989 by Wim Leers, Crell: Move replacing of placeholders from...

Issue #2551989 by Wim Leers, Crell: Move replacing of placeholders from HtmlRenderer to HtmlResponseAttachmentsProcessor
parent 1234fa61
......@@ -943,7 +943,7 @@ services:
- { name: event_subscriber }
main_content_renderer.html:
class: Drupal\Core\Render\MainContent\HtmlRenderer
arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@module_handler', '@renderer', '@render_cache']
arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@module_handler', '@renderer', '@render_cache', '%renderer.config%']
tags:
- { name: render.main_content_renderer, format: html }
main_content_renderer.ajax:
......
......@@ -1294,7 +1294,7 @@ function template_preprocess_html(&$variables) {
'@token' => $token,
]);
$variables[$type]['#markup'] = $placeholder;
$variables[$type]['#attached']['html_response_placeholders'][$type] = $placeholder;
$variables[$type]['#attached']['html_response_attachment_placeholders'][$type] = $placeholder;
}
}
......
......@@ -36,12 +36,16 @@ public function setContent($content) {
// A render array can automatically be converted to a string and set the
// necessary metadata.
if (is_array($content) && (isset($content['#markup']))) {
$content += ['#attached' => ['html_response_placeholders' => []]];
$content += ['#attached' => [
'html_response_attachment_placeholders' => [],
'placeholders' => []],
];
$this->addCacheableDependency(CacheableMetadata::createFromRenderArray($content));
$this->setAttachments($content['#attached']);
$content = $content['#markup'];
}
parent::setContent($content);
return parent::setContent($content);
}
}
......@@ -99,25 +99,31 @@ public function processAttachments(AttachmentsInterface $response) {
throw new \InvalidArgumentException('\Drupal\Core\Render\HtmlResponse instance expected.');
}
// First, render the actual placeholders; this may cause additional
// attachments to be added to the response, which the attachment
// placeholders rendered by renderHtmlResponseAttachmentPlaceholders() will
// need to include.
$response = $this->renderPlaceholders($response);
$attached = $response->getAttachments();
// Get the placeholders from attached and then remove them.
$placeholders = $attached['html_response_placeholders'];
unset($attached['html_response_placeholders']);
$attachment_placeholders = $attached['html_response_attachment_placeholders'];
unset($attached['html_response_attachment_placeholders']);
$variables = $this->processAssetLibraries($attached, $placeholders);
$variables = $this->processAssetLibraries($attached, $attachment_placeholders);
// Handle all non-asset attachments. This populates drupal_get_html_head().
$all_attached = ['#attached' => $attached];
drupal_process_attached($all_attached);
// Get HTML head elements - if present.
if (isset($placeholders['head'])) {
if (isset($attachment_placeholders['head'])) {
$variables['head'] = drupal_get_html_head(FALSE);
}
// Now replace the placeholders in the response content with the real data.
$this->renderPlaceholders($response, $placeholders, $variables);
// Now replace the attachment placeholders.
$this->renderHtmlResponseAttachmentPlaceholders($response, $attachment_placeholders, $variables);
// Finally set the headers on the response if any bubbled.
if (!empty($attached['http_header'])) {
......@@ -127,6 +133,55 @@ public function processAttachments(AttachmentsInterface $response) {
return $response;
}
/**
* Renders placeholders (#attached['placeholders']).
*
* First, the HTML response object is converted to an equivalent render array,
* with #markup being set to the response's content and #attached being set to
* the response's attachments. Among these attachments, there may be
* placeholders that need to be rendered (replaced).
*
* Next, RendererInterface::renderRoot() is called, which renders the
* placeholders into their final markup.
*
* The markup that results from RendererInterface::renderRoot() is now the
* original HTML response's content, but with the placeholders rendered. We
* overwrite the existing content in the original HTML response object with
* this markup. The markup that was rendered for the placeholders may also
* have attachments (e.g. for CSS/JS assets) itself, and cacheability metadata
* that indicates what that markup depends on. That metadata is also added to
* the HTML response object.
*
* @param \Drupal\Core\Render\HtmlResponse $response
* The HTML response whose placeholders are being replaced.
*
* @return \Drupal\Core\Render\HtmlResponse
* The updated HTML response, with replaced placeholders.
*
* @see \Drupal\Core\Render\Renderer::replacePlaceholders()
* @see \Drupal\Core\Render\Renderer::renderPlaceholder()
*/
protected function renderPlaceholders(HtmlResponse $response) {
$build = [
'#markup' => SafeString::create($response->getContent()),
'#attached' => $response->getAttachments(),
];
// RendererInterface::renderRoot() renders the $build render array and
// updates it in place. We don't care about the return value (which is just
// $build['#markup']), but about the resulting render array.
// @todo Simplify this when https://www.drupal.org/node/2495001 lands.
$this->renderer->renderRoot($build);
// Update the Response object now that the placeholders have been rendered.
$placeholders_bubbleable_metadata = BubbleableMetadata::createFromRenderArray($build);
$response
->setContent($build['#markup'])
->addCacheableDependency($placeholders_bubbleable_metadata)
->setAttachments($placeholders_bubbleable_metadata->getAttachments());
return $response;
}
/**
* Processes asset libraries into render arrays.
*
......@@ -174,8 +229,7 @@ protected function processAssetLibraries(array $attached, array $placeholders) {
}
/**
* Renders variables into HTML markup and replaces placeholders in the
* response content.
* Renders HTML response attachment placeholders.
*
* @param \Drupal\Core\Render\HtmlResponse $response
* The HTML response to update.
......@@ -186,7 +240,7 @@ protected function processAssetLibraries(array $attached, array $placeholders) {
* The variables to render and replace, keyed by type with renderable
* arrays as values.
*/
protected function renderPlaceholders(HtmlResponse $response, array $placeholders, array $variables) {
protected function renderHtmlResponseAttachmentPlaceholders(HtmlResponse $response, array $placeholders, array $variables) {
$content = $response->getContent();
foreach ($placeholders as $type => $placeholder) {
if (isset($variables[$type])) {
......
......@@ -8,9 +8,11 @@
namespace Drupal\Core\Render\MainContent;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Controller\TitleResolverInterface;
use Drupal\Core\Display\PageVariantInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\HtmlResponse;
use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
use Drupal\Core\Render\RenderCacheInterface;
......@@ -74,6 +76,15 @@ class HtmlRenderer implements MainContentRendererInterface {
*/
protected $renderCache;
/**
* The renderer configuration array.
*
* @see sites/default/default.services.yml
*
* @var array
*/
protected $rendererConfig;
/**
* Constructs a new HtmlRenderer.
*
......@@ -89,14 +100,17 @@ class HtmlRenderer implements MainContentRendererInterface {
* The renderer service.
* @param \Drupal\Core\Render\RenderCacheInterface $render_cache
* The render cache service.
* @param array $renderer_config
* The renderer configuration array.
*/
public function __construct(TitleResolverInterface $title_resolver, PluginManagerInterface $display_variant_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, RendererInterface $renderer, RenderCacheInterface $render_cache) {
public function __construct(TitleResolverInterface $title_resolver, PluginManagerInterface $display_variant_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, RendererInterface $renderer, RenderCacheInterface $render_cache, array $renderer_config) {
$this->titleResolver = $title_resolver;
$this->displayVariantManager = $display_variant_manager;
$this->eventDispatcher = $event_dispatcher;
$this->moduleHandler = $module_handler;
$this->renderer = $renderer;
$this->renderCache = $render_cache;
$this->rendererConfig = $renderer_config;
}
/**
......@@ -125,11 +139,29 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
// page.html.twig, hence add them here, just before rendering html.html.twig.
$this->buildPageTopAndBottom($html);
// @todo https://www.drupal.org/node/2495001 Make renderRoot return a
// cacheable render array directly.
$this->renderer->renderRoot($html);
// Render, but don't replace placeholders yet, because that happens later in
// the render pipeline. To not replace placeholders yet, we use
// RendererInterface::render() instead of RendererInterface::renderRoot().
// @see \Drupal\Core\Render\HtmlResponseAttachmentsProcessor.
$render_context = new RenderContext();
$this->renderer->executeInRenderContext($render_context, function() use (&$html) {
// RendererInterface::render() renders the $html render array and updates
// it in place. We don't care about the return value (which is just
// $html['#markup']), but about the resulting render array.
// @todo Simplify this when https://www.drupal.org/node/2495001 lands.
$this->renderer->render($html);
});
// RendererInterface::render() always causes bubbleable metadata to be
// stored in the render context, no need to check it conditionally.
$bubbleable_metadata = $render_context->pop();
$bubbleable_metadata->applyTo($html);
$content = $this->renderCache->getCacheableRenderArray($html);
// Also associate the required cache contexts.
// (Because we use ::render() above and not ::renderRoot(), we manually must
// ensure the HTML response varies by the required cache contexts.)
$content['#cache']['contexts'] = Cache::mergeContexts($content['#cache']['contexts'], $this->rendererConfig['required_cache_contexts']);
// Also associate the "rendered" cache tag. This allows us to invalidate the
// entire render cache, regardless of the cache bin.
$content['#cache']['tags'][] = 'rendered';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment