df72915a8ed674a6e73a505839d929421ecc2c01
[yaffs-website] / web / core / modules / big_pipe / js / big_pipe.es6.js
1 /**
2  * @file
3  * Renders BigPipe placeholders using Drupal's Ajax system.
4  */
5
6 (function ($, Drupal, drupalSettings) {
7   /**
8    * Executes Ajax commands in <script type="application/vnd.drupal-ajax"> tag.
9    *
10    * These Ajax commands replace placeholders with HTML and load missing CSS/JS.
11    *
12    * @param {number} index
13    *   Current index.
14    * @param {HTMLScriptElement} placeholderReplacement
15    *   Script tag created by BigPipe.
16    */
17   function bigPipeProcessPlaceholderReplacement(index, placeholderReplacement) {
18     const placeholderId = placeholderReplacement.getAttribute('data-big-pipe-replacement-for-placeholder-with-id');
19     const content = this.textContent.trim();
20     // Ignore any placeholders that are not in the known placeholder list. Used
21     // to avoid someone trying to XSS the site via the placeholdering mechanism.
22     if (typeof drupalSettings.bigPipePlaceholderIds[placeholderId] !== 'undefined') {
23       const response = mapTextContentToAjaxResponse(content);
24       // If we try to parse the content too early (when the JSON containing Ajax
25       // commands is still arriving), textContent will be empty or incomplete.
26       if (response === false) {
27         /**
28          * Mark as unprocessed so this will be retried later.
29          * @see bigPipeProcessDocument()
30          */
31         $(this).removeOnce('big-pipe');
32       }
33       else {
34         // Create a Drupal.Ajax object without associating an element, a
35         // progress indicator or a URL.
36         const ajaxObject = Drupal.ajax({
37           url: '',
38           base: false,
39           element: false,
40           progress: false,
41         });
42         // Then, simulate an AJAX response having arrived, and let the Ajax
43         // system handle it.
44         ajaxObject.success(response, 'success');
45       }
46     }
47   }
48
49   /**
50    * Maps textContent of <script type="application/vnd.drupal-ajax"> to an AJAX response.
51    *
52    * @param {string} content
53    *   The text content of a <script type="application/vnd.drupal-ajax"> DOM node.
54    * @return {Array|boolean}
55    *   The parsed Ajax response containing an array of Ajax commands, or false in
56    *   case the DOM node hasn't fully arrived yet.
57    */
58   function mapTextContentToAjaxResponse(content) {
59     if (content === '') {
60       return false;
61     }
62
63     try {
64       return JSON.parse(content);
65     }
66     catch (e) {
67       return false;
68     }
69   }
70
71   /**
72    * Processes a streamed HTML document receiving placeholder replacements.
73    *
74    * @param {HTMLDocument} context
75    *   The HTML document containing <script type="application/vnd.drupal-ajax">
76    *   tags generated by BigPipe.
77    *
78    * @return {bool}
79    *   Returns true when processing has been finished and a stop signal has been
80    *   found.
81    */
82   function bigPipeProcessDocument(context) {
83     // Make sure we have BigPipe-related scripts before processing further.
84     if (!context.querySelector('script[data-big-pipe-event="start"]')) {
85       return false;
86     }
87
88     $(context).find('script[data-big-pipe-replacement-for-placeholder-with-id]')
89       .once('big-pipe')
90       .each(bigPipeProcessPlaceholderReplacement);
91
92     // If we see the stop signal, clear the timeout: all placeholder
93     // replacements are guaranteed to be received and processed.
94     if (context.querySelector('script[data-big-pipe-event="stop"]')) {
95       if (timeoutID) {
96         clearTimeout(timeoutID);
97       }
98       return true;
99     }
100
101     return false;
102   }
103
104   function bigPipeProcess() {
105     timeoutID = setTimeout(() => {
106       if (!bigPipeProcessDocument(document)) {
107         bigPipeProcess();
108       }
109     }, interval);
110   }
111
112   // The frequency with which to check for newly arrived BigPipe placeholders.
113   // Hence 50 ms means we check 20 times per second. Setting this to 100 ms or
114   // more would cause the user to see content appear noticeably slower.
115   const interval = drupalSettings.bigPipeInterval || 50;
116   // The internal ID to contain the watcher service.
117   let timeoutID;
118
119   bigPipeProcess();
120
121   // If something goes wrong, make sure everything is cleaned up and has had a
122   // chance to be processed with everything loaded.
123   $(window).on('load', () => {
124     if (timeoutID) {
125       clearTimeout(timeoutID);
126     }
127     bigPipeProcessDocument(document);
128   });
129 }(jQuery, Drupal, drupalSettings));