src/Render/BigPipe.php | 27 ++++++++++++++-------- src/Render/Placeholder/BigPipeStrategy.php | 23 ++++++++++-------- .../Render/Placeholder/BigPipeStrategyTest.php | 16 ++++++------- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/Render/BigPipe.php b/src/Render/BigPipe.php index 7099414..0b4250c 100644 --- a/src/Render/BigPipe.php +++ b/src/Render/BigPipe.php @@ -195,18 +195,26 @@ class BigPipe implements BigPipeInterface { * BigPipe placeholders. */ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) { - $fragments = explode('
', $fragment, 2); - $placeholder = $t[0]; - if (!isset($no_js_placeholders[$placeholder])) { + // Split the HTML on every no-JS placeholder string. + $prepare_for_preg_split = function ($placeholder_string) { + return '(' . preg_quote($placeholder_string, '/') . ')'; + }; + $preg_placeholder_strings = array_map($prepare_for_preg_split, array_keys($no_js_placeholders)); + $fragments = preg_split('/' . implode('|', $preg_placeholder_strings) . '/', $html, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + assert('count($fragments) % 2 === 1'); + + foreach ($fragments as $index => $fragment) { + // The fragments with an even index (0, 2, 4 …) contain HTML which needs + // to be printed and flushed right away. The rest of the logic in the loop + // handles the uneven indices (1, 3, 5 …) which contains placeholders. + if ($index % 2 === 0) { + print $fragment; + flush(); continue; } + $placeholder = $fragment; + assert('isset($no_js_placeholders[$placeholder])'); $token = Crypt::randomBytesBase64(55); // Render the placeholder, but include the cumulative settings assets, so @@ -252,7 +260,6 @@ class BigPipe implements BigPipeInterface { // Send this embedded HTML response. print $html_response->getContent(); - print $t[1]; flush(); // Another placeholder was rendered and sent, track the set of asset diff --git a/src/Render/Placeholder/BigPipeStrategy.php b/src/Render/Placeholder/BigPipeStrategy.php index 6ae4973..bf4ea76 100644 --- a/src/Render/Placeholder/BigPipeStrategy.php +++ b/src/Render/Placeholder/BigPipeStrategy.php @@ -132,13 +132,13 @@ class BigPipeStrategy implements PlaceholderStrategyInterface { // @see \Drupal\Core\Form\FormBuilder::renderFormTokenPlaceholder() // @see \Drupal\Core\Form\FormBuilder::renderPlaceholderFormAction() if ($placeholder[0] !== '<' || $placeholder !== Html::normalize($placeholder)) { - $overridden_placeholders[$placeholder] = static::createBigPipeNoJsPlaceholder($placeholder, $placeholder_elements); + $overridden_placeholders[$placeholder] = static::createBigPipeNoJsPlaceholder($placeholder, $placeholder_elements, TRUE); } else { // If the current request/session doesn't have JavaScript, fall back to // no-JS BigPipe. if ($this->requestStack->getCurrentRequest()->cookies->has(static::NOJS_COOKIE)) { - $overridden_placeholders[$placeholder] = static::createBigPipeNoJsPlaceholder($placeholder, $placeholder_elements); + $overridden_placeholders[$placeholder] = static::createBigPipeNoJsPlaceholder($placeholder, $placeholder_elements, FALSE); } else { $overridden_placeholders[$placeholder] = static::createBigPipeJsPlaceholder($placeholder, $placeholder_elements); @@ -194,18 +194,23 @@ class BigPipeStrategy implements PlaceholderStrategyInterface { * The original placeholder. * @param array $placeholder_render_array * The render array for a placeholder. + * @param bool $placeholder_must_be_attribute_safe + * Whether the placeholder must be safe for use in a HTML attribute (in case + * it's a placeholder for a HTML attribute value or a subset of it). * * @return array * The resulting BigPipe no-JS placeholder render array. - * - * @todo Figure out how to simplify this. Perhaps no new placeholder is in fact necessary? - * @todo Related, perhaps distinguish between "HTML" and "non-HTML (attr value)" use cases? Because right now, this *breaks* HTML and therefore breaks response filters: this indiscriminately uses a