big_pipe.es6.js 4.2 KB

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