Initial Drupal 11 with DDEV setup
This commit is contained in:
183
web/core/modules/big_pipe/js/big_pipe.js
Normal file
183
web/core/modules/big_pipe/js/big_pipe.js
Normal file
@ -0,0 +1,183 @@
|
||||
/**
|
||||
* @file
|
||||
* Renders BigPipe placeholders using Drupal's Ajax system.
|
||||
*/
|
||||
|
||||
((Drupal, drupalSettings) => {
|
||||
/**
|
||||
* CSS selector for script elements to process on page load.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
const replacementsSelector = `script[data-big-pipe-replacement-for-placeholder-with-id]`;
|
||||
|
||||
/**
|
||||
* Ajax object that will process all the BigPipe responses.
|
||||
*
|
||||
* Create a Drupal.Ajax object without associating an element, a progress
|
||||
* indicator or a URL.
|
||||
*
|
||||
* @type {Drupal.Ajax}
|
||||
*/
|
||||
const ajaxObject = Drupal.ajax({
|
||||
url: '',
|
||||
base: false,
|
||||
element: false,
|
||||
progress: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 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 {HTMLScriptElement} replacement
|
||||
* Script tag created by BigPipe.
|
||||
*/
|
||||
function processReplacement(replacement) {
|
||||
const id = replacement.dataset.bigPipeReplacementForPlaceholderWithId;
|
||||
// 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
|
||||
// to avoid someone trying to XSS the site via the placeholdering mechanism.
|
||||
if (typeof drupalSettings.bigPipePlaceholderIds[id] === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
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?.bigPipeReplacementForPlaceholderWithId &&
|
||||
typeof drupalSettings.bigPipePlaceholderIds[
|
||||
node.dataset.bigPipeReplacementForPlaceholderWithId
|
||||
] !== 'undefined',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the element is valid to process and process it.
|
||||
*
|
||||
* @param {HTMLElement} node
|
||||
* The node added to the body element.
|
||||
*/
|
||||
function checkMutationAndProcess(node) {
|
||||
if (checkMutation(node)) {
|
||||
processReplacement(node);
|
||||
}
|
||||
// Checks if parent node of target node has not been processed, which can
|
||||
// occur if the script node was first observed with empty content and then
|
||||
// the child text node was added in full later.
|
||||
// @see `@ingroup large_chunk` for more information.
|
||||
// If an element is added and then immediately (faster than the next
|
||||
// setImmediate is triggered) removed to a watched element of a
|
||||
// MutationObserver, the observer will notice and add a mutation for both
|
||||
// the addedNode and the removedNode - but the referenced element will not
|
||||
// have a parent node.
|
||||
else if (node.parentNode !== null && checkMutation(node.parentNode)) {
|
||||
processReplacement(node.parentNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mutation callback.
|
||||
*
|
||||
* @param {MutationRecord[]} mutations
|
||||
* The list of mutations registered by the browser.
|
||||
*/
|
||||
function processMutations(mutations) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(processMutations);
|
||||
|
||||
// Attach behaviors early, if possible.
|
||||
Drupal.attachBehaviors(document);
|
||||
|
||||
// If loaded asynchronously there might already be replacement elements
|
||||
// in the DOM before the mutation observer is started.
|
||||
document.querySelectorAll(replacementsSelector).forEach(processReplacement);
|
||||
|
||||
// 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.
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const mutations = observer.takeRecords();
|
||||
observer.disconnect();
|
||||
if (mutations.length) {
|
||||
processMutations(mutations);
|
||||
}
|
||||
// No more mutations will be processed, remove the leftover Ajax object.
|
||||
Drupal.ajax.instances[ajaxObject.instanceIndex] = null;
|
||||
});
|
||||
})(Drupal, drupalSettings);
|
||||
Reference in New Issue
Block a user