diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php index 7d36b51..69a8f13 100644 --- a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php +++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php @@ -148,6 +148,16 @@ protected function buildAttachmentsCommands(AjaxResponse $response, Request $req $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css); list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js); + // First, AttachedAssets::setLibraries() ensures duplicate libraries are + // removed: it converts it to a set of libraries if necessary. Second, + // AssetResolver::getJsSettings() ensures $assets contains the final set of + // JavaScript settings. AttachmentsResponseProcessorInterface also mandates + // that the response it processes contains the final attachment values, so + // update both the 'library' and 'drupalSettings' attachments accordingly. + $attachments['library'] = $assets->getLibraries(); + $attachments['drupalSettings'] = $assets->getSettings(); + $response->setAttachments($attachments); + // Render the HTML to load these files, and add AJAX commands to insert this // HTML in the page. Settings are handled separately, afterwards. $settings = []; diff --git a/core/lib/Drupal/Core/Asset/AssetResolver.php b/core/lib/Drupal/Core/Asset/AssetResolver.php index 9927049..a533c8a 100644 --- a/core/lib/Drupal/Core/Asset/AssetResolver.php +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php @@ -334,6 +334,9 @@ public function getJsAssets(AttachedAssetsInterface $assets, $optimize) { // Allow modules and themes to alter the JavaScript settings. $this->moduleHandler->alter('js_settings', $settings, $assets); $this->themeManager->alter('js_settings', $settings, $assets); + // Update the $assets object accordingly, so that it reflects the final + // settings. + $assets->setSettings($settings); $settings_as_inline_javascript = [ 'type' => 'setting', 'group' => JS_SETTING, diff --git a/core/lib/Drupal/Core/Asset/AssetResolverInterface.php b/core/lib/Drupal/Core/Asset/AssetResolverInterface.php index c912d76..e0845c6 100644 --- a/core/lib/Drupal/Core/Asset/AssetResolverInterface.php +++ b/core/lib/Drupal/Core/Asset/AssetResolverInterface.php @@ -69,6 +69,8 @@ public function getCssAssets(AttachedAssetsInterface $assets, $optimize); * * @param \Drupal\Core\Asset\AttachedAssetsInterface $assets * The assets attached to the current response. + * Note that this object is modified to reflect the final JavaScript + * settings assets. * @param bool $optimize * Whether to apply the JavaScript asset collection optimizer, to return * optimized JavaScript asset collections rather than an unoptimized ones. diff --git a/core/lib/Drupal/Core/Render/AttachmentsResponseProcessorInterface.php b/core/lib/Drupal/Core/Render/AttachmentsResponseProcessorInterface.php index 6067a86..74614ee 100644 --- a/core/lib/Drupal/Core/Render/AttachmentsResponseProcessorInterface.php +++ b/core/lib/Drupal/Core/Render/AttachmentsResponseProcessorInterface.php @@ -46,7 +46,8 @@ * The response to process. * * @return \Drupal\Core\Render\AttachmentsInterface - * The processed response. + * The processed response, with the attachments updated to reflect their + * final values. * * @throws \InvalidArgumentException * Thrown when the $response parameter is not the type of response object diff --git a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php index e0160d1..d407062 100644 --- a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php +++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php @@ -9,6 +9,7 @@ use Drupal\Core\Asset\AssetCollectionRendererInterface; use Drupal\Core\Asset\AssetResolverInterface; use Drupal\Core\Asset\AttachedAssets; +use Drupal\Core\Asset\AttachedAssetsInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\EnforcedResponseException; use Drupal\Core\Extension\ModuleHandlerInterface; @@ -155,7 +156,19 @@ public function processAttachments(AttachmentsInterface $response) { $attachment_placeholders = $attached['html_response_attachment_placeholders']; unset($attached['html_response_attachment_placeholders']); - $variables = $this->processAssetLibraries($attached, $attachment_placeholders); + $assets = AttachedAssets::createFromRenderArray(['#attached' => $attached]); + // Take Ajax page state into account, to allow for something like + // Turbolinks to be implemented without altering core. + // @see https://github.com/rails/turbolinks/ + $ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state'); + $assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []); + $variables = $this->processAssetLibraries($assets, $attachment_placeholders); + // $variables now contains the markup to load the asset libraries. Update + // $attached with the final list of libraries and JavaScript settings, so + // that $response can be updated with those. Then the response object will + // list the final, processed attachments. + $attached['library'] = $assets->getLibraries(); + $attached['drupalSettings'] = $assets->getSettings(); // Since we can only replace content in the HTML head section if there's a // placeholder for it, we can safely avoid processing the render array if @@ -168,6 +181,7 @@ public function processAttachments(AttachmentsInterface $response) { $attached, $this->processFeed($attached['feed']) ); + unset($attached['feed']); } // 'html_head_link' is a special case of 'html_head' which can be present // as a head element, but also as a Link: HTTP header depending on @@ -182,6 +196,7 @@ public function processAttachments(AttachmentsInterface $response) { $attached, $this->processHtmlHeadLink($attached['html_head_link']) ); + unset($attached['html_head_link']); } // Now we can process 'html_head', which contains both 'feed' and @@ -200,6 +215,10 @@ public function processAttachments(AttachmentsInterface $response) { $this->setHeaders($response, $attached['http_header']); } + // AttachmentsResponseProcessorInterface mandates that the response it + // processes contains the final attachment values. + $response->setAttachments($attached); + return $response; } @@ -255,8 +274,8 @@ protected function renderPlaceholders(HtmlResponse $response) { /** * Processes asset libraries into render arrays. * - * @param array $attached - * The attachments to process. + * @param \Drupal\Core\Asset\AttachedAssetsInterface $assets + * The attached assets collection for the current response. * @param array $placeholders * The placeholders that exist in the response. * @@ -266,16 +285,7 @@ protected function renderPlaceholders(HtmlResponse $response) { * - scripts * - scripts_bottom */ - protected function processAssetLibraries(array $attached, array $placeholders) { - $all_attached = ['#attached' => $attached]; - $assets = AttachedAssets::createFromRenderArray($all_attached); - - // Take Ajax page state into account, to allow for something like Turbolinks - // to be implemented without altering core. - // @see https://github.com/rails/turbolinks/ - $ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state'); - $assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []); - + protected function processAssetLibraries(AttachedAssetsInterface $assets, array $placeholders) { $variables = []; // Print styles - if present. diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index b1f7c2a..9d62dfa 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -158,24 +158,9 @@ public function renderPlain(&$elements) { } /** - * Renders final HTML for a placeholder. - * - * Renders the placeholder in isolation. - * - * @param string $placeholder - * An attached placeholder to render. (This must be a key of one of the - * values of $elements['#attached']['placeholders'].) - * @param array $elements - * The structured array describing the data to be rendered. - * - * @return array - * The updated $elements. - * - * @see ::replacePlaceholders() - * - * @todo Make public as part of https://www.drupal.org/node/2469431 + * {@inheritdoc} */ - protected function renderPlaceholder($placeholder, array $elements) { + public function renderPlaceholder($placeholder, array $elements) { // Get the render array for the given placeholder $placeholder_elements = $elements['#attached']['placeholders'][$placeholder]; @@ -196,7 +181,6 @@ protected function renderPlaceholder($placeholder, array $elements) { return $elements; } - /** * {@inheritdoc} */ @@ -647,6 +631,8 @@ protected function setCurrentRenderContext(RenderContext $context = NULL) { * * @returns bool * Whether placeholders were replaced. + * + * @see ::renderPlaceholder() */ protected function replacePlaceholders(array &$elements) { if (!isset($elements['#attached']['placeholders']) || empty($elements['#attached']['placeholders'])) { diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php index ff04d86..ccbe2d5 100644 --- a/core/lib/Drupal/Core/Render/RendererInterface.php +++ b/core/lib/Drupal/Core/Render/RendererInterface.php @@ -67,6 +67,24 @@ public function renderRoot(&$elements); public function renderPlain(&$elements); /** + * Renders final HTML for a placeholder. + * + * Renders the placeholder in isolation. + * + * @param string $placeholder + * An attached placeholder to render. (This must be a key of one of the + * values of $elements['#attached']['placeholders'].) + * @param array $elements + * The structured array describing the data to be rendered. + * + * @return array + * The updated $elements. + * + * @see ::render() + */ + public function renderPlaceholder($placeholder, array $elements); + + /** * Renders HTML given a structured array tree. * * Renderable arrays have two kinds of key/value pairs: properties and diff --git a/core/modules/big_pipe/big_pipe.info.yml b/core/modules/big_pipe/big_pipe.info.yml new file mode 100644 index 0000000..b0f6e91 --- /dev/null +++ b/core/modules/big_pipe/big_pipe.info.yml @@ -0,0 +1,6 @@ +name: BigPipe +type: module +description: 'Enables BigPipe for authenticated users; first send+render the cheap parts of the page, then the expensive parts.' +package: Core +version: VERSION +core: 8.x diff --git a/core/modules/big_pipe/big_pipe.libraries.yml b/core/modules/big_pipe/big_pipe.libraries.yml new file mode 100644 index 0000000..88ecff6 --- /dev/null +++ b/core/modules/big_pipe/big_pipe.libraries.yml @@ -0,0 +1,11 @@ +big_pipe: + version: VERSION + js: + js/big_pipe.js: {} + drupalSettings: + bigPipePlaceholders: [] + dependencies: + - core/jquery + - core/drupal + - core/drupal.ajax + - core/drupalSettings diff --git a/core/modules/big_pipe/big_pipe.module b/core/modules/big_pipe/big_pipe.module new file mode 100644 index 0000000..f2615e6 --- /dev/null +++ b/core/modules/big_pipe/big_pipe.module @@ -0,0 +1,56 @@ + 'hidden', + '#default_value' => '1', + ); + + $form['#after_build'][] = 'big_pipe_form_after_build'; + $form['#submit'][] = 'big_pipe_form_set_js_check'; +} + +/** + * After build handler for user_login_form(). + */ +function big_pipe_form_after_build($form, FormStateInterface $form_state) { + // This is tricky: we want Form API to 'default big_pipe_has_js' to '1' in + // case it is not sent. We also want to set the value of the HTML element + // to '0' and wrap it in a