/**
* @file
* Renders BigPipe placeholders using Drupal's Ajax system.
*/
(function ($, Drupal, drupalSettings) {
/**
* Maps textContent of <script type="application/vnd.drupal-ajax"> to an AJAX response.
*
* @param {string} content
* The text content of a <script type="application/vnd.drupal-ajax"> DOM node.
* @return {Array|boolean}
* The parsed Ajax response containing an array of Ajax commands, or false in
* case the DOM node hasn't fully arrived yet.
*/
function mapTextContentToAjaxResponse(content) {
if (content === '') {
return false;
}
try {
return JSON.parse(content);
} catch (e) {
return false;
}
}
/**
* Executes Ajax commands in <script type="application/vnd.drupal-ajax"> tag.
*
* These Ajax commands replace placeholders with HTML and load missing CSS/JS.
*
* @param {number} index
* Current index.
* @param {HTMLScriptElement} placeholderReplacement
* Script tag created by BigPipe.
*/
function bigPipeProcessPlaceholderReplacement(index, placeholderReplacement) {
const placeholderId = placeholderReplacement.getAttribute(
'data-big-pipe-replacement-for-placeholder-with-id',
);
const content = this.textContent.trim();
// Ignore any placeholders that are not in the known placeholder list. Used
// to avoid someone trying to XSS the site via the placeholdering mechanism.
if (
typeof drupalSettings.bigPipePlaceholderIds[placeholderId] !== 'undefined'
) {
const response = mapTextContentToAjaxResponse(content);
// If we try to parse the content too early (when the JSON containing Ajax
// commands is still arriving), textContent will be empty or incomplete.
if (response === false) {
/**
* Mark as unprocessed so this will be retried later.
* @see bigPipeProcessDocument()
*/
$(this).removeOnce('big-pipe');
} else {
// Create a Drupal.Ajax object without associating an element, a
// progress indicator or a URL.
const ajaxObject = Drupal.ajax({
url: '',
base: false,
element: false,
progress: false,
});
// Then, simulate an AJAX response having arrived, and let the Ajax
// system handle it.
ajaxObject.success(response, 'success');
}
}
}
// The frequency with which to check for newly arrived BigPipe placeholders.
// Hence 50 ms means we check 20 times per second. Setting this to 100 ms or
// more would cause the user to see content appear noticeably slower.
const interval = drupalSettings.bigPipeInterval || 50;
// The internal ID to contain the watcher service.
let timeoutID;
/**
* Processes a streamed HTML document receiving placeholder replacements.
*
* @param {HTMLDocument} context
* The HTML document containing <script type="application/vnd.drupal-ajax">
* tags generated by BigPipe.
*
* @return {bool}
* Returns true when processing has been finished and a stop signal has been
* found.
*/
function bigPipeProcessDocument(context) {
// Make sure we have BigPipe-related scripts before processing further.
if (!context.querySelector('script[data-big-pipe-event="start"]')) {
return false;
}
$(context)
.find('script[data-big-pipe-replacement-for-placeholder-with-id]')
.once('big-pipe')
.each(bigPipeProcessPlaceholderReplacement);
// If we see the stop signal, clear the timeout: all placeholder
// replacements are guaranteed to be received and processed.
if (context.querySelector('script[data-big-pipe-event="stop"]')) {
if (timeoutID) {
clearTimeout(timeoutID);
}
return true;
}
return false;
}
function bigPipeProcess() {
timeoutID = setTimeout(() => {
if (!bigPipeProcessDocument(document)) {
bigPipeProcess();
}
}, interval);
}
bigPipeProcess();
// If something goes wrong, make sure everything is cleaned up and has had a
// chance to be processed with everything loaded.
$(window).on('load', () => {
if (timeoutID) {
clearTimeout(timeoutID);
}
bigPipeProcessDocument(document);
});
})(jQuery, Drupal, drupalSettings);