diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php index 7d36b51..80bc7f9 100644 --- a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php +++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php @@ -105,16 +105,19 @@ public function __construct(AssetResolverInterface $asset_resolver, ConfigFactor /** * {@inheritdoc} */ - public function processAttachments(AttachmentsInterface $response) { + public function processAttachments(AttachmentsInterface $response, $ajax_page_state = NULL) { // @todo Convert to assertion once https://www.drupal.org/node/2408013 lands if (!$response instanceof AjaxResponse) { throw new \InvalidArgumentException('\Drupal\Core\Ajax\AjaxResponse instance expected.'); } - $request = $this->requestStack->getCurrentRequest(); - if ($response->getContent() == '{}') { - $response->setData($this->buildAttachmentsCommands($response, $request)); + if (!isset($ajax_page_state)) { + $request = $this->requestStack->getCurrentRequest(); + $ajax_page_state = $request->request->get('ajax_page_state'); + } + + $response->setData($this->buildAttachmentsCommands($response, $ajax_page_state)); } return $response; @@ -125,15 +128,13 @@ public function processAttachments(AttachmentsInterface $response) { * * @param \Drupal\Core\Ajax\AjaxResponse $response * The AJAX response to update. - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object that the AJAX is responding to. + * @param array $ajax_page_state + * The current ajax page state. * * @return array * An array of commands ready to be returned as JSON. */ - protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) { - $ajax_page_state = $request->request->get('ajax_page_state'); - + protected function buildAttachmentsCommands(AjaxResponse $response, array $ajax_page_state = NULL) { // Aggregate CSS/JS if necessary, but only during normal site operation. $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess'); $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess'); diff --git a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php index e0160d1..93251b5 100644 --- a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php +++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php @@ -141,9 +141,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 +156,13 @@ public function processAttachments(AttachmentsInterface $response) { $attachment_placeholders = $attached['html_response_attachment_placeholders']; unset($attached['html_response_attachment_placeholders']); - $variables = $this->processAssetLibraries($attached, $attachment_placeholders); + // Take Ajax page state into account, to allow for something like Turbolinks + // to be implemented without altering core. + // @see https://github.com/rails/turbolinks/ + // @todo https://www.drupal.org/node/2497115 - Below line is broken due to ->request. + $ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state'); + + $variables = $this->processAssetLibraries($attached, $attachment_placeholders, $ajax_page_state); // 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 @@ -259,6 +266,8 @@ protected function renderPlaceholders(HtmlResponse $response) { * The attachments to process. * @param array $placeholders * The placeholders that exist in the response. + * @param array $ajax_page_state + * (optional) The ajax page state of the page. * * @return array * An array keyed by asset type, with keys: @@ -266,14 +275,10 @@ protected function renderPlaceholders(HtmlResponse $response) { * - scripts * - scripts_bottom */ - protected function processAssetLibraries(array $attached, array $placeholders) { + public function processAssetLibraries(array $attached, array $placeholders, array $ajax_page_state = NULL) { $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']) : []); $variables = []; @@ -290,7 +295,13 @@ protected function processAssetLibraries(array $attached, array $placeholders) { // Optimize JS if necessary, but only during normal site operation. $optimize_js = !defined('MAINTENANCE_MODE') && !\Drupal::state()->get('system.maintenance_mode') && $this->config->get('js.preprocess'); list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js); + } + + if (isset($placeholders['scripts'])) { $variables['scripts'] = $this->jsCollectionRenderer->render($js_assets_header); + } + + if (isset($placeholders['scripts_bottom'])) { $variables['scripts_bottom'] = $this->jsCollectionRenderer->render($js_assets_footer); } 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..bae1715 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -175,7 +175,7 @@ public function renderPlain(&$elements) { * * @todo Make public as part of https://www.drupal.org/node/2469431 */ - 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]; @@ -341,6 +341,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. 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..0fdda3d --- /dev/null +++ b/core/modules/big_pipe/big_pipe.module @@ -0,0 +1,66 @@ +setAjaxPageState(isset($settings['ajaxPageState']) ? $settings['ajaxPageState'] : NULL); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function big_pipe_form_user_login_form_alter(&$form, FormStateInterface $form_state, $form_id) { + // Check if the user has JavaScript enabled without adding JavaScript. + $form['big_pipe_has_js'] = array( + '#type' => '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 gets not send. We also want to set the value of the HTML element + // to 0 and add