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
as a placeholder, which is invalid inside a HTML attribute, and thus breaks DOM parsing. */ - protected static function createBigPipeNoJsPlaceholder($original_placeholder, array $placeholder_render_array) { - $big_pipe_placeholder_id = static::generateBigPipePlaceholderId($original_placeholder, $placeholder_render_array); + protected static function createBigPipeNoJsPlaceholder($original_placeholder, array $placeholder_render_array, $placeholder_must_be_attribute_safe = FALSE) { + if (!$placeholder_must_be_attribute_safe) { + $big_pipe_placeholder = '
'; + } + else { + $big_pipe_placeholder = 'big_pipe_nojs_placeholder_attribute_safe:' . Html::escape($original_placeholder); + } return [ - '#markup' => '
', + '#markup' => $big_pipe_placeholder, '#cache' => [ 'max-age' => 0, 'contexts' => [ @@ -214,7 +219,7 @@ class BigPipeStrategy implements PlaceholderStrategyInterface { ], '#attached' => [ 'big_pipe_nojs_placeholders' => [ - $big_pipe_placeholder_id => $placeholder_render_array, + $big_pipe_placeholder => $placeholder_render_array, ], ], ]; diff --git a/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php b/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php index 35babb8..48cb167 100644 --- a/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php +++ b/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php @@ -90,11 +90,11 @@ class BigPipeStrategyTest extends UnitTestCase { ], ]; $messages_big_pipe_nojs_placeholder = [ - '#markup' => '
', + '#markup' => '
', '#cache' => $cacheability_depends_on_session_and_nojs_cookie, '#attached' => [ 'big_pipe_nojs_placeholders' => [ - 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e' => $placeholders[$messages_placeholder_markup], + '
' => $placeholders[$messages_placeholder_markup], ], ], ]; @@ -104,11 +104,11 @@ class BigPipeStrategyTest extends UnitTestCase { '#lazy_builder' => ['form_builder:renderPlaceholderFormAction', []], ]; $form_action_big_pipe_nojs_placeholder = [ - '#markup' => '
', + '#markup' => 'big_pipe_nojs_placeholder_attribute_safe:form_action_cc611e1d', '#cache' => $cacheability_depends_on_session_only, '#attached' => [ 'big_pipe_nojs_placeholders' => [ - 'callback=form_builder%3ArenderPlaceholderFormAction&&token=e8cb6dae' => $placeholders['form_action_cc611e1d'], + 'big_pipe_nojs_placeholder_attribute_safe:form_action_cc611e1d' => $placeholders['form_action_cc611e1d'], ], ], ]; @@ -121,11 +121,11 @@ class BigPipeStrategyTest extends UnitTestCase { ], ]; $csrf_token_big_pipe_nojs_placeholder = [ - '#markup' => '
', + '#markup' => 'big_pipe_nojs_placeholder_attribute_safe:22dcc695f4a22e809a62d8eb7addd6fccecfbf31', '#cache' => $cacheability_depends_on_session_only, '#attached' => [ 'big_pipe_nojs_placeholders' => [ - 'callback=route_processor_csrf%3ArenderPlaceholderCsrfToken&args[0]=admin/config/user-interface/shortcut/manage/default/add-link-inline&token=77d1dce6' => $placeholders['22dcc695f4a22e809a62d8eb7addd6fccecfbf31'], + 'big_pipe_nojs_placeholder_attribute_safe:22dcc695f4a22e809a62d8eb7addd6fccecfbf31' => $placeholders['22dcc695f4a22e809a62d8eb7addd6fccecfbf31'], ], ], ]; @@ -139,11 +139,11 @@ class BigPipeStrategyTest extends UnitTestCase { ], ]; $hello_big_pipe_nojs_placeholder = [ - '#markup' => '
', + '#markup' => 'big_pipe_nojs_placeholder_attribute_safe:<hello', '#cache' => $cacheability_depends_on_session_only, '#attached' => [ 'big_pipe_nojs_placeholders' => [ - 'callback=hello_or_yarhar&&token=d4524bc8' => $placeholders[' $placeholders['