Issue #2981584 by Wim Leers, JvE, ndobromirov: BigPipe breaks on large amounts of placeholders (e.g. Flag module on view displaying ~1000 entities)
parent
63e448fefe
commit
0f73e39753
|
@ -389,14 +389,11 @@ class BigPipe {
|
||||||
*/
|
*/
|
||||||
protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) {
|
protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) {
|
||||||
// Split the HTML on every no-JS placeholder string.
|
// Split the HTML on every no-JS placeholder string.
|
||||||
$prepare_for_preg_split = function ($placeholder_string) {
|
$placeholder_strings = array_keys($no_js_placeholders);
|
||||||
return '(' . preg_quote($placeholder_string, '/') . ')';
|
$fragments = static::splitHtmlOnPlaceholders($html, $placeholder_strings);
|
||||||
};
|
|
||||||
$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);
|
|
||||||
|
|
||||||
// Determine how many occurrences there are of each no-JS placeholder.
|
// 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
|
// Set up a variable to store the content of placeholders that have multiple
|
||||||
// occurrences.
|
// occurrences.
|
||||||
|
@ -754,4 +751,39 @@ EOF;
|
||||||
return $ordered_placeholder_ids;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue