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