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..69e3a4b 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; @@ -141,9 +142,10 @@ public function processAttachments(AttachmentsInterface $response) { $attached = $response->getAttachments(); // Send a message back if the render array has unsupported #attached types. + // @todo This needs to be configurable somehow. $unsupported_types = array_diff( array_keys($attached), - ['html_head', 'feed', 'html_head_link', 'http_header', 'library', 'html_response_attachment_placeholders', 'placeholders', 'drupalSettings'] + ['html_head', 'feed', 'html_head_link', 'http_header', 'library', 'html_response_attachment_placeholders', 'placeholders', 'drupalSettings', 'big_pipe_placeholders'] ); if (!empty($unsupported_types)) { throw new \LogicException(sprintf('You are not allowed to use %s in #attached.', implode(', ', $unsupported_types))); @@ -155,7 +157,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 +182,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 +197,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 +216,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 +275,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 +286,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/PlaceholderGenerator.php b/core/lib/Drupal/Core/Render/PlaceholderGenerator.php index e0940bf..51d7c77 100644 --- a/core/lib/Drupal/Core/Render/PlaceholderGenerator.php +++ b/core/lib/Drupal/Core/Render/PlaceholderGenerator.php @@ -80,6 +80,8 @@ public function createPlaceholder(array $element) { // The cacheability metadata for the placeholder. The rendered result of // the placeholder may itself be cached, if [#cache][keys] are specified. '#cache' => TRUE, + // The options for creating the placeholder. (optional) + '#create_placeholder_options' => TRUE, ]); // Generate placeholder markup. Note that the only requirement is that this diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index b1f7c2a..c1cbc0a 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} */ @@ -341,6 +325,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) { '#lazy_builder', '#cache', '#create_placeholder', + '#create_placeholder_options', // These keys are not actually supported, but they are added automatically // by the Renderer, so we don't crash on them; them being missing when // their #lazy_builder callback is invoked won't surprise the developer. @@ -647,6 +632,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/lib/Drupal/Core/StackMiddleware/Session.php b/core/lib/Drupal/Core/StackMiddleware/Session.php index e0ed0e4..e42f1a7 100644 --- a/core/lib/Drupal/Core/StackMiddleware/Session.php +++ b/core/lib/Drupal/Core/StackMiddleware/Session.php @@ -59,13 +59,7 @@ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = $request->setSession($session); } - $result = $this->httpKernel->handle($request, $type, $catch); - - if ($type === self::MASTER_REQUEST && $request->hasSession()) { - $request->getSession()->save(); - } - - return $result; + return $this->httpKernel->handle($request, $type, $catch); } } 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