From 0f73e39753d30d767dfb752d34eff548dbcbfa4a Mon Sep 17 00:00:00 2001 From: catch Date: Tue, 16 Apr 2019 15:25:40 +0900 Subject: [PATCH] Issue #2981584 by Wim Leers, JvE, ndobromirov: BigPipe breaks on large amounts of placeholders (e.g. Flag module on view displaying ~1000 entities) --- core/modules/big_pipe/src/Render/BigPipe.php | 44 ++++++++++++-- .../src/Unit/Render/ManyPlaceholderTest.php | 59 +++++++++++++++++++ 2 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 core/modules/big_pipe/tests/src/Unit/Render/ManyPlaceholderTest.php diff --git a/core/modules/big_pipe/src/Render/BigPipe.php b/core/modules/big_pipe/src/Render/BigPipe.php index e646348d837d..7397cd53c23a 100644 --- a/core/modules/big_pipe/src/Render/BigPipe.php +++ b/core/modules/big_pipe/src/Render/BigPipe.php @@ -389,14 +389,11 @@ class BigPipe { */ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) { // 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); + $placeholder_strings = array_keys($no_js_placeholders); + $fragments = static::splitHtmlOnPlaceholders($html, $placeholder_strings); // Determine how many occurrences there are of each no-JS placeholder. - $placeholder_occurrences = array_count_values(array_intersect($fragments, array_keys($no_js_placeholders))); + $placeholder_occurrences = array_count_values(array_intersect($fragments, $placeholder_strings)); // Set up a variable to store the content of placeholders that have multiple // occurrences. @@ -754,4 +751,39 @@ EOF; return $ordered_placeholder_ids; } + /** + * Splits a HTML string into fragments. + * + * Creates an array of HTML fragments, separated by placeholders. The result + * includes the placeholders themselves. The original order is respected. + * + * @param string $html_string + * The HTML to split. + * @param string[] $html_placeholders + * The HTML placeholders to split on. + * + * @return string[] + * The resulting HTML fragments. + */ + private static function splitHtmlOnPlaceholders($html_string, array $html_placeholders) { + $prepare_for_preg_split = function ($placeholder_string) { + return '(' . preg_quote($placeholder_string, '/') . ')'; + }; + $preg_placeholder_strings = array_map($prepare_for_preg_split, $html_placeholders); + $pattern = '/' . implode('|', $preg_placeholder_strings) . '/'; + if (strlen($pattern) < 31000) { + // Only small (<31K characters) patterns can be handled by preg_split(). + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE; + $result = preg_split($pattern, $html_string, NULL, $flags); + } + else { + // For large amounts of placeholders we use a simpler but slower approach. + foreach ($html_placeholders as $placeholder) { + $html_string = str_replace($placeholder, "\x1F" . $placeholder . "\x1F", $html_string); + } + $result = array_filter(explode("\x1F", $html_string)); + } + return $result; + } + } diff --git a/core/modules/big_pipe/tests/src/Unit/Render/ManyPlaceholderTest.php b/core/modules/big_pipe/tests/src/Unit/Render/ManyPlaceholderTest.php new file mode 100644 index 000000000000..3de77ec16a26 --- /dev/null +++ b/core/modules/big_pipe/tests/src/Unit/Render/ManyPlaceholderTest.php @@ -0,0 +1,59 @@ +prophesize(RendererInterface::class)->reveal(), + $this->prophesize(SessionInterface::class)->reveal(), + $this->prophesize(RequestStack::class)->reveal(), + $this->prophesize(HttpKernelInterface::class)->reveal(), + $this->prophesize(EventDispatcherInterface::class)->reveal(), + $this->prophesize(ConfigFactoryInterface::class)->reveal() + ); + $response = new BigPipeResponse(HtmlResponse::create()); + + // Add many placeholders. + $many_placeholders = []; + for ($i = 0; $i < 400; $i++) { + $many_placeholders[$this->randomMachineName(80)] = $this->randomMachineName(80); + } + $attachments = [ + 'library' => [], + 'big_pipe_nojs_placeholders' => $many_placeholders, + ]; + $response->setAttachments($attachments); + + // Construct minimal HTML response. + $content = 'contentscript-bottom'; + $response->setContent($content); + + // Capture the result to avoid PHPUnit complaining. + ob_start(); + $bigpipe->sendContent($response); + $result = ob_get_clean(); + + $this->assertNotEmpty($result); + } + +}