Skip to content
Snippets Groups Projects
Commit 09bcf41f authored by catch's avatar catch
Browse files

Issue #3387039 by dinazaur, smustgrave, fjgarlin, nod_: Large placeholders are not processed

(cherry picked from commit 6f97533d)
parent a6ba4811
No related branches found
No related tags found
11 merge requests!7564Revert "Issue #3364773 by roshnichordiya, Chris Matthews, thakurnishant_06,...,!5752Issue #3275828 by joachim, quietone, bradjones1, Berdir: document the reason...,!5627Issue #3261805: Field not saved when change of 0 on string start,!5427Issue #3338518: send credentials in ajax if configured in CORS settings.,!5395Issue #3387916 by fjgarlin, Spokje: Each GitLab job exposes user email,!5217Issue #3386607 by alexpott: Improve spell checking in commit-code-check.sh,!5064Issue #3379522 by finnsky, Gauravvvv, kostyashupenko, smustgrave, Chi: Revert...,!5040SDC ComponentElement: Transform slots scalar values to #plain_text instead of throwing an exception,!4894Issue #3280279: Add API to allow sites to opt in to upload SVG images in CKEditor 5,!3106Issue #3017548: "Filtered HTML" text format does not support manual teaser break (<!--break-->),!872Draft: Issue #3221319: Race condition when creating menu links and editing content deletes menu links
Pipeline #29353 passed
+1
......@@ -59,8 +59,9 @@
*/
function processReplacement(replacement) {
const id = replacement.dataset.bigPipeReplacementForPlaceholderWithId;
// Because we use a mutation observer the content is guaranteed to be
// complete at this point.
// The content is not guaranteed to be complete at this point, but trimming
// it will not make a big change, since json will not be valid if it was
// not fully loaded anyway.
const content = replacement.textContent.trim();
// Ignore any placeholders that are not in the known placeholder list. Used
......@@ -69,20 +70,32 @@
return;
}
// Immediately remove the replacement to prevent it being processed twice.
delete drupalSettings.bigPipePlaceholderIds[id];
const response = mapTextContentToAjaxResponse(content);
if (response === false) {
return;
}
// Immediately remove the replacement to prevent it being processed twice.
delete drupalSettings.bigPipePlaceholderIds[id];
// Then, simulate an AJAX response having arrived, and let the Ajax system
// handle it.
ajaxObject.success(response, 'success');
}
/**
* Checks if node is valid big pipe replacement.
*/
function checkMutation(node) {
return Boolean(
node.nodeType === Node.ELEMENT_NODE &&
node.nodeName === 'SCRIPT' &&
node.dataset &&
node.dataset.bigPipeReplacementForPlaceholderWithId,
);
}
/**
* Check that the element is valid to process and process it.
*
......@@ -90,12 +103,7 @@
* The node added to the body element.
*/
function checkMutationAndProcess(node) {
if (
node.nodeType === Node.ELEMENT_NODE &&
node.nodeName === 'SCRIPT' &&
node.dataset &&
node.dataset.bigPipeReplacementForPlaceholderWithId
) {
if (checkMutation(node)) {
processReplacement(node);
}
}
......@@ -107,8 +115,20 @@
* The list of mutations registered by the browser.
*/
function processMutations(mutations) {
mutations.forEach(({ addedNodes }) => {
mutations.forEach(({ addedNodes, type, target }) => {
addedNodes.forEach(checkMutationAndProcess);
// Checks if parent node of target node has not been processed.
// @see `@ingroup large_chunk` for more information.
if (
type === 'characterData' &&
checkMutation(target.parentNode) &&
drupalSettings.bigPipePlaceholderIds[
target.parentNode.dataset.bigPipeReplacementForPlaceholderWithId
] === true
) {
processReplacement(target.parentNode);
}
});
}
......@@ -121,8 +141,19 @@
// in the DOM before the mutation observer is started.
document.querySelectorAll(replacementsSelector).forEach(processReplacement);
// Start observing the body element for new children.
observer.observe(document.body, { childList: true });
// Start observing the body element for new children and for new changes in
// Text nodes of elements. We need to track Text nodes because content
// of the node can be too large, browser will receive not fully loaded chunk
// and render it as is. At this moment json inside script will be invalid and
// we need to track new changes to that json (Text node), once it will be
// fully loaded it will be processed.
// @ingroup large_chunk
observer.observe(document.body, {
childList: true,
// Without this options characterData will not be triggered inside child nodes.
subtree: true,
characterData: true,
});
// As soon as the document is loaded, no more replacements will be added.
// Immediately fetch and process all pending mutations and stop the observer.
......
<?php
/**
* @file
* Support module for BigPipe testing.
*/
/**
* Implements hook_theme().
*
* @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testBigPipeLargeContent
*/
function big_pipe_regression_test_theme() {
return [
'big_pipe_test_large_content' => [
'variables' => [],
],
];
}
......@@ -11,3 +11,11 @@ big_pipe_regression_test.2802923:
_controller: '\Drupal\big_pipe_regression_test\BigPipeRegressionTestController::regression2802923'
requirements:
_access: 'TRUE'
big_pipe_test_large_content:
path: '/big_pipe_test_large_content'
defaults:
_controller: '\Drupal\big_pipe_regression_test\BigPipeRegressionTestController::largeContent'
_title: 'BigPipe test large content'
requirements:
_access: 'TRUE'
......@@ -32,6 +32,32 @@ public function regression2802923() {
];
}
/**
* A page with large content.
*
* @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testBigPipeLargeContent
*/
public function largeContent() {
return [
'item1' => [
'#lazy_builder' => [static::class . '::largeContentBuilder', []],
'#create_placeholder' => TRUE,
],
];
}
/**
* Renders large content.
*
* @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testBigPipeLargeContent
*/
public static function largeContentBuilder() {
return [
'#theme' => 'big_pipe_test_large_content',
'#cache' => ['max-age' => 0],
];
}
/**
* #lazy_builder callback; builds <time> markup with current time.
*
......@@ -48,7 +74,7 @@ public static function currentTime() {
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return ['currentTime'];
return ['currentTime', 'largeContentBuilder'];
}
}
<div id="big-pipe-large-content">
{% for i in 0..130000 %}
boing
{% endfor %}
</div>
......@@ -124,4 +124,28 @@ public function testPlaceholderInParagraph_2802923() {
$this->assertJsCondition('document.querySelectorAll(\'p\').length === 1');
}
/**
* Tests BigPipe large content.
*
* Repeat loading of same page for two times, after second time the page is
* cached and the bug consistently reproducible.
*/
public function testBigPipeLargeContent() {
$user = $this->drupalCreateUser();
$this->drupalLogin($user);
$assert_session = $this->assertSession();
$this->drupalGet(Url::fromRoute('big_pipe_test_large_content'));
$this->assertNotNull($assert_session->waitForElement('css', 'script[data-big-pipe-event="stop"]'));
$this->assertCount(0, $this->getDrupalSettings()['bigPipePlaceholderIds']);
$this->assertCount(2, $this->getSession()->getPage()->findAll('css', 'script[data-big-pipe-replacement-for-placeholder-with-id]'));
$assert_session->elementExists('css', '#big-pipe-large-content');
$this->drupalGet(Url::fromRoute('big_pipe_test_large_content'));
$this->assertNotNull($assert_session->waitForElement('css', 'script[data-big-pipe-event="stop"]'));
$this->assertCount(0, $this->getDrupalSettings()['bigPipePlaceholderIds']);
$this->assertCount(2, $this->getSession()->getPage()->findAll('css', 'script[data-big-pipe-replacement-for-placeholder-with-id]'));
$assert_session->elementExists('css', '#big-pipe-large-content');
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment