Issue #2551989 by Wim Leers, Crell: Move replacing of placeholders from HtmlRenderer to HtmlResponseAttachmentsProcessor

8.0.x
Alex Pott 2015-08-18 16:44:17 +01:00
parent 1234fa6190
commit 0549d59349
5 changed files with 107 additions and 17 deletions

View File

@ -943,7 +943,7 @@ services:
- { name: event_subscriber } - { name: event_subscriber }
main_content_renderer.html: main_content_renderer.html:
class: Drupal\Core\Render\MainContent\HtmlRenderer 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: tags:
- { name: render.main_content_renderer, format: html } - { name: render.main_content_renderer, format: html }
main_content_renderer.ajax: main_content_renderer.ajax:

View File

@ -1294,7 +1294,7 @@ function template_preprocess_html(&$variables) {
'@token' => $token, '@token' => $token,
]); ]);
$variables[$type]['#markup'] = $placeholder; $variables[$type]['#markup'] = $placeholder;
$variables[$type]['#attached']['html_response_placeholders'][$type] = $placeholder; $variables[$type]['#attached']['html_response_attachment_placeholders'][$type] = $placeholder;
} }
} }

View File

@ -36,12 +36,16 @@ class HtmlResponse extends Response implements CacheableResponseInterface, Attac
// A render array can automatically be converted to a string and set the // A render array can automatically be converted to a string and set the
// necessary metadata. // necessary metadata.
if (is_array($content) && (isset($content['#markup']))) { 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->addCacheableDependency(CacheableMetadata::createFromRenderArray($content));
$this->setAttachments($content['#attached']); $this->setAttachments($content['#attached']);
$content = $content['#markup']; $content = $content['#markup'];
} }
parent::setContent($content); return parent::setContent($content);
} }
} }

View File

@ -99,25 +99,31 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
throw new \InvalidArgumentException('\Drupal\Core\Render\HtmlResponse instance expected.'); 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(); $attached = $response->getAttachments();
// Get the placeholders from attached and then remove them. // Get the placeholders from attached and then remove them.
$placeholders = $attached['html_response_placeholders']; $attachment_placeholders = $attached['html_response_attachment_placeholders'];
unset($attached['html_response_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(). // Handle all non-asset attachments. This populates drupal_get_html_head().
$all_attached = ['#attached' => $attached]; $all_attached = ['#attached' => $attached];
drupal_process_attached($all_attached); drupal_process_attached($all_attached);
// Get HTML head elements - if present. // Get HTML head elements - if present.
if (isset($placeholders['head'])) { if (isset($attachment_placeholders['head'])) {
$variables['head'] = drupal_get_html_head(FALSE); $variables['head'] = drupal_get_html_head(FALSE);
} }
// Now replace the placeholders in the response content with the real data. // Now replace the attachment placeholders.
$this->renderPlaceholders($response, $placeholders, $variables); $this->renderHtmlResponseAttachmentPlaceholders($response, $attachment_placeholders, $variables);
// Finally set the headers on the response if any bubbled. // Finally set the headers on the response if any bubbled.
if (!empty($attached['http_header'])) { if (!empty($attached['http_header'])) {
@ -127,6 +133,55 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
return $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. * Processes asset libraries into render arrays.
* *
@ -174,8 +229,7 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
} }
/** /**
* Renders variables into HTML markup and replaces placeholders in the * Renders HTML response attachment placeholders.
* response content.
* *
* @param \Drupal\Core\Render\HtmlResponse $response * @param \Drupal\Core\Render\HtmlResponse $response
* The HTML response to update. * The HTML response to update.
@ -186,7 +240,7 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
* The variables to render and replace, keyed by type with renderable * The variables to render and replace, keyed by type with renderable
* arrays as values. * arrays as values.
*/ */
protected function renderPlaceholders(HtmlResponse $response, array $placeholders, array $variables) { protected function renderHtmlResponseAttachmentPlaceholders(HtmlResponse $response, array $placeholders, array $variables) {
$content = $response->getContent(); $content = $response->getContent();
foreach ($placeholders as $type => $placeholder) { foreach ($placeholders as $type => $placeholder) {
if (isset($variables[$type])) { if (isset($variables[$type])) {

View File

@ -8,9 +8,11 @@
namespace Drupal\Core\Render\MainContent; namespace Drupal\Core\Render\MainContent;
use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Controller\TitleResolverInterface; use Drupal\Core\Controller\TitleResolverInterface;
use Drupal\Core\Display\PageVariantInterface; use Drupal\Core\Display\PageVariantInterface;
use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\HtmlResponse; use Drupal\Core\Render\HtmlResponse;
use Drupal\Core\Render\PageDisplayVariantSelectionEvent; use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
use Drupal\Core\Render\RenderCacheInterface; use Drupal\Core\Render\RenderCacheInterface;
@ -74,6 +76,15 @@ class HtmlRenderer implements MainContentRendererInterface {
*/ */
protected $renderCache; protected $renderCache;
/**
* The renderer configuration array.
*
* @see sites/default/default.services.yml
*
* @var array
*/
protected $rendererConfig;
/** /**
* Constructs a new HtmlRenderer. * Constructs a new HtmlRenderer.
* *
@ -89,14 +100,17 @@ class HtmlRenderer implements MainContentRendererInterface {
* The renderer service. * The renderer service.
* @param \Drupal\Core\Render\RenderCacheInterface $render_cache * @param \Drupal\Core\Render\RenderCacheInterface $render_cache
* The render cache service. * 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->titleResolver = $title_resolver;
$this->displayVariantManager = $display_variant_manager; $this->displayVariantManager = $display_variant_manager;
$this->eventDispatcher = $event_dispatcher; $this->eventDispatcher = $event_dispatcher;
$this->moduleHandler = $module_handler; $this->moduleHandler = $module_handler;
$this->renderer = $renderer; $this->renderer = $renderer;
$this->renderCache = $render_cache; $this->renderCache = $render_cache;
$this->rendererConfig = $renderer_config;
} }
/** /**
@ -125,11 +139,29 @@ class HtmlRenderer implements MainContentRendererInterface {
// page.html.twig, hence add them here, just before rendering html.html.twig. // page.html.twig, hence add them here, just before rendering html.html.twig.
$this->buildPageTopAndBottom($html); $this->buildPageTopAndBottom($html);
// @todo https://www.drupal.org/node/2495001 Make renderRoot return a // Render, but don't replace placeholders yet, because that happens later in
// cacheable render array directly. // the render pipeline. To not replace placeholders yet, we use
$this->renderer->renderRoot($html); // 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); $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 // Also associate the "rendered" cache tag. This allows us to invalidate the
// entire render cache, regardless of the cache bin. // entire render cache, regardless of the cache bin.
$content['#cache']['tags'][] = 'rendered'; $content['#cache']['tags'][] = 'rendered';