Commit 0f73e397 authored by catch's avatar catch

Issue #2981584 by Wim Leers, JvE, ndobromirov: BigPipe breaks on large amounts...

Issue #2981584 by Wim Leers, JvE, ndobromirov: BigPipe breaks on large amounts of placeholders (e.g. Flag module on view displaying ~1000 entities)
parent 63e448fe
......@@ -389,14 +389,11 @@ protected function sendPreBody($pre_body, array $no_js_placeholders, AttachedAss
*/
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 @@ protected function getPlaceholderOrder($html, $placeholders) {
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;
}
}
<?php
namespace Drupal\Tests\big_pipe\Unit\Render;
use Drupal\big_pipe\Render\BigPipe;
use Drupal\big_pipe\Render\BigPipeResponse;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Render\HtmlResponse;
use Drupal\Core\Render\RendererInterface;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* @coversDefaultClass \Drupal\big_pipe\Render\BigPipe
* @group big_pipe
*/
class ManyPlaceholderTest extends UnitTestCase {
/**
* @covers \Drupal\big_pipe\Render\BigPipe::sendNoJsPlaceholders
*/
public function testManyNoJsPlaceHolders() {
$bigpipe = new BigPipe(
$this->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 = '<html><body>content<drupal-big-pipe-scripts-bottom-marker>script-bottom<drupal-big-pipe-scripts-bottom-marker></body></html>';
$response->setContent($content);
// Capture the result to avoid PHPUnit complaining.
ob_start();
$bigpipe->sendContent($response);
$result = ob_get_clean();
$this->assertNotEmpty($result);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment