diff --git a/core/modules/big_pipe/src/Render/BigPipe.php b/core/modules/big_pipe/src/Render/BigPipe.php
index 3e67737f54401351a20284e8cc1c4461992e6250..5e3cc120320e3cdbbf95f2a97fbddaed710d305e 100644
--- a/core/modules/big_pipe/src/Render/BigPipe.php
+++ b/core/modules/big_pipe/src/Render/BigPipe.php
@@ -226,6 +226,13 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse
$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.
+ $placeholder_occurrences = array_count_values(array_intersect($fragments, array_keys($no_js_placeholders)));
+
+ // Set up a variable to store the content of placeholders that have multiple
+ // occurrences.
+ $multi_occurrence_placeholders_content = [];
+
foreach ($fragments as $fragment) {
// If the fragment isn't one of the no-JS placeholders, it is the HTML in
// between placeholders and it must be printed & flushed immediately. The
@@ -236,6 +243,15 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse
continue;
}
+ // If there are multiple occurrences of this particular placeholder, and
+ // this is the second occurrence, we can skip all calculations and just
+ // send the same content.
+ if ($placeholder_occurrences[$fragment] > 1 && isset($multi_occurrence_placeholders_content[$fragment])) {
+ print $multi_occurrence_placeholders_content[$fragment];
+ flush();
+ continue;
+ }
+
$placeholder = $fragment;
assert('isset($no_js_placeholders[$placeholder])');
$token = Crypt::randomBytesBase64(55);
@@ -310,6 +326,13 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse
// they can be sent in ::sendPreBody().
$cumulative_assets->setAlreadyLoadedLibraries(array_merge($cumulative_assets->getAlreadyLoadedLibraries(), $html_response->getAttachments()['library']));
$cumulative_assets->setSettings($html_response->getAttachments()['drupalSettings']);
+
+ // If there are multiple occurrences of this particular placeholder, track
+ // the content that was sent, so we can skip all calculations for the next
+ // occurrence.
+ if ($placeholder_occurrences[$fragment] > 1) {
+ $multi_occurrence_placeholders_content[$fragment] = $html_response->getContent();
+ }
}
}
@@ -508,7 +531,9 @@ protected function renderPlaceholder($placeholder, array $placeholder_render_arr
*
* @return array
* Indexed array; the order in which the BigPipe placeholders must be sent.
- * Values are the BigPipe placeholder IDs.
+ * Values are the BigPipe placeholder IDs. Note that only unique
+ * placeholders are kept: if the same placeholder occurs multiple times, we
+ * only keep the first occurrence.
*/
protected function getPlaceholderOrder($html) {
$fragments = explode('
';
+ $this->assertRaw('The count is 1.');
+ $this->assertNoRaw('The count is 2.');
+ $this->assertNoRaw('The count is 3.');
+ $raw_content = $this->getRawContent();
+ $this->assertTrue(substr_count($raw_content, $expected_placeholder_replacement) == 1, 'Only one placeholder replacement was found for the duplicate #lazy_builder arrays.');
+
+ // By calling performMetaRefresh() here, we simulate JavaScript being
+ // disabled, because as far as the BigPipe module is concerned, it is
+ // enabled in the browser when the BigPipe no-JS cookie is set.
+ // @see setUp()
+ // @see performMetaRefresh()
+ $this->performMetaRefresh();
+ $this->assertBigPipeNoJsCookieExists(TRUE);
+ $this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence'));
+ $this->assertRaw('The count is 1.');
+ $this->assertNoRaw('The count is 2.');
+ $this->assertNoRaw('The count is 3.');
+ }
+
protected function assertBigPipeResponseHeadersPresent() {
$this->pass('Verifying BigPipe response headers…', 'Debug');
$this->assertTrue(FALSE !== strpos($this->drupalGetHeader('Cache-Control'), 'private'), 'Cache-Control header set to "private".');
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml b/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml
index 4979406d5a96564a7056215b6b760ddd5e08e931..710506063054d05af46d868a5c567382afe62f3a 100644
--- a/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml
+++ b/core/modules/big_pipe/tests/modules/big_pipe_test/big_pipe_test.routing.yml
@@ -15,3 +15,12 @@ no_big_pipe:
_no_big_pipe: TRUE
requirements:
_access: 'TRUE'
+
+big_pipe_test_multi_occurrence:
+ path: '/big_pipe_test_multi_occurrence'
+ defaults:
+ _controller: '\Drupal\big_pipe_test\BigPipeTestController::multiOccurrence'
+ _title: 'BigPipe test multiple occurrences of the same placeholder'
+ requirements:
+ _access: 'TRUE'
+
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
index 450a464bd467eaea40db3657e74535e68a9ed5db..30594a55530398f6870880dd95d9fd47c8d5414b 100644
--- a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
+++ b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
@@ -52,6 +52,30 @@ public static function nope() {
return ['#markup' => '