.../HtmlResponseBigPipeSubscriber.php | 27 ++-
core/modules/big_pipe/src/Render/BigPipe.php | 195 ++++++++++++---------
.../big_pipe/src/Render/BigPipeInterface.php | 71 ++++++++
.../big_pipe/src/Render/BigPipeResponse.php | 2 +-
.../Render/BigPipeResponseAttachmentsProcessor.php | 2 +-
.../src/Render/Placeholder/BigPipeStrategy.php | 2 +-
6 files changed, 205 insertions(+), 94 deletions(-)
diff --git a/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php b/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php
index 2af2dbe..98a41cb 100644
--- a/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php
+++ b/core/modules/big_pipe/src/EventSubscriber/HtmlResponseBigPipeSubscriber.php
@@ -17,7 +17,7 @@
/**
* Response subscriber to replace the HtmlResponse with a BigPipeResponse.
*
- * @see \Drupal\big_pipe\Render\BigPipe
+ * @see \Drupal\big_pipe\Render\BigPipeInterface
*
* @todo Refactor once https://www.drupal.org/node/2577631 lands.
*/
@@ -52,15 +52,14 @@ public function onRespondEarly(FilterResponseEvent $event) {
return;
}
- // Set a marker around 'scripts_bottom'
+ // Wrap the scripts_bottom placeholder with a marker before and after,
+ // because \Drupal\big_pipe\Render\BigPipe needs to be able to extract that
+ // markup if there are no-JS BigPipe placeholders.
+ // @see \Drupal\big_pipe\Render\BigPipe::sendPreBody()
$attachments = $response->getAttachments();
if (isset($attachments['html_response_attachment_placeholders']['scripts_bottom'])) {
$scripts_bottom_placeholder = $attachments['html_response_attachment_placeholders']['scripts_bottom'];
$content = $response->getContent();
-
- // Wrap the scripts_bottom placeholder with a marker before and after,
- // because \Drupal\big_pipe\Render\BigPipe needs to be able to parse out
- // that placeholder for the "HalfPipe" render strategy.
$content = str_replace($scripts_bottom_placeholder, '' . $scripts_bottom_placeholder . '', $content);
$response->setContent($content);
}
@@ -83,15 +82,25 @@ public function onRespond(FilterResponseEvent $event) {
}
$attachments = $response->getAttachments();
- if (empty($attachments['big_pipe_placeholders']) && empty($attachments['big_pipe_nojs_placeholders'])) {
- // Remove our marker again.
+
+ // If there are no no-JS BigPipe placeholders, unwrap the scripts_bottom
+ // markup.
+ // @see onRespondEarly()
+ // @see \Drupal\big_pipe\Render\BigPipe::sendPreBody()
+ if (empty($attachments['big_pipe_nojs_placeholders'])) {
$content = $response->getContent();
$content = str_replace('', '', $content);
$response->setContent($content);
+ }
+
+ // If there are neither BigPipe placeholders nor no-JS BigPipe placeholders,
+ // there isn't anything dynamic in this response, and we can return early:
+ // there is no point in sending this response using BigPipe.
+ if (empty($attachments['big_pipe_placeholders']) && empty($attachments['big_pipe_nojs_placeholders'])) {
return;
}
- // Create a new Response.
+ // Create a new BigPipeResponse.
$big_pipe_response = new BigPipeResponse();
$big_pipe_response->setBigPipeService($this->bigPipe);
diff --git a/core/modules/big_pipe/src/Render/BigPipe.php b/core/modules/big_pipe/src/Render/BigPipe.php
index d7b5a61..15f1c10 100644
--- a/core/modules/big_pipe/src/Render/BigPipe.php
+++ b/core/modules/big_pipe/src/Render/BigPipe.php
@@ -11,6 +11,7 @@
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Asset\AttachedAssets;
+use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
@@ -25,8 +26,7 @@
use Symfony\Component\HttpKernel\KernelEvents;
/**
- * A class that allows sending the main content first, then replace
- * placeholders to send the rest using Javascript replacements.
+ * The default BigPipe service.
*/
class BigPipe implements BigPipeInterface {
@@ -91,87 +91,108 @@ public function __construct(RendererInterface $renderer, SessionInterface $sessi
* {@inheritdoc}
*/
public function sendContent($content, array $attachments) {
- // We are sending a BigPipeResponse in this method. A BigPipeResponse is an
- // aggregated response: it consists of a HtmlResponse plus multiple embedded
- // AjaxResponses. The embedded AjaxResponses are generated here, in this
- // method: one for each placeholder that needs to be replaced. This means
- // that each AjaxResponse needs to be aware of the asset libraries that have
- // already been loaded by the initial HtmlResponse plus all the preceding
- // AjaxResponses. An AttachedAssetsInterface object is a perfect way to
- // track this over time.
- $assets = AttachedAssets::createFromRenderArray(['#attached' => $attachments]);
- $assets->setAlreadyLoadedLibraries(explode(',', $attachments['drupalSettings']['ajaxPageState']['libraries']));
+ // First, gather the BigPipe placeholders that must be replaced.
+ $placeholders = isset($attachments['big_pipe_placeholders']) ? $attachments['big_pipe_placeholders'] : [];
+ $nojs_placeholders = isset($attachments['big_pipe_nojs_placeholders']) ? $attachments['big_pipe_nojs_placeholders'] : [];
+
+ // BigPipe sends responses using "Transfer-Encoding: chunked". To avoid
+ // sending already-sent assets, it is necessary to track cumulative assets
+ // from all previously rendered/sent chunks.
+ // @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41
+ $cumulative_assets = AttachedAssets::createFromRenderArray(['#attached' => $attachments]);
+ $cumulative_assets->setAlreadyLoadedLibraries(explode(',', $attachments['drupalSettings']['ajaxPageState']['libraries']));
// The content in the placeholders may depend on the session, and by the
// time the response is sent (see index.php), the session is already closed.
// Reopen it for the duration that we are rendering placeholders.
$this->session->start();
- // Extract the scripts_bottom markup; the HalfPipe render strategy needs to
- // be able to update it.
- $t = explode('', $content, 3);
- assert('count($t) == 3', 'There is content before and after scripts_bottom.');
- $scripts_bottom = $t[1];
- unset($t[1]);
- $content = implode('', $t);
-
- // Split it up in various chunks.
- $page_parts = explode('