Loading core/lib/Drupal/Core/Theme/Registry.php +34 −10 Original line number Diff line number Diff line Loading @@ -237,22 +237,46 @@ protected function init($theme_name = NULL) { */ public function get() { $this->init($this->themeName); if (isset($this->registry[$this->theme->getName()])) { return $this->registry[$this->theme->getName()]; if ($cached = $this->cacheGet()) { return $cached; } // If called from inside a Fiber, suspend it, this may allow another code // path to begin an asynchronous operation before we do the CPU-intensive // task of building the theme registry. if (\Fiber::getCurrent() !== NULL) { \Fiber::suspend(); // When the Fiber is resumed, check the cache again since it may have been // built in the meantime, either in this process or via a different // request altogether. if ($cached = $this->cacheGet()) { return $cached; } if ($cache = $this->cache->get('theme_registry:' . $this->theme->getName())) { $this->registry[$this->theme->getName()] = $cache->data; } else { $this->build(); // Only persist it if all modules are loaded to ensure it is complete. if ($this->moduleHandler->isLoaded()) { $this->setCache(); } } return $this->registry[$this->theme->getName()]; } /** * Gets the theme registry cache. * * @return array|null */ protected function cacheGet(): ?array { $theme_name = $this->theme->getName(); if (isset($this->registry[$theme_name])) { return $this->registry[$theme_name]; } elseif ($cache = $this->cache->get('theme_registry:' . $theme_name)) { $this->registry[$theme_name] = $cache->data; return $this->registry[$theme_name]; } return NULL; } /** * Returns the incomplete, runtime theme registry. * Loading core/modules/big_pipe/src/Render/BigPipe.php +96 −62 Original line number Diff line number Diff line Loading @@ -536,59 +536,77 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde $fake_request = $this->requestStack->getMainRequest()->duplicate(); $fake_request->headers->set('Accept', 'application/vnd.drupal-ajax'); // Create a Fiber for each placeholder. $fibers = []; $message_placeholder_id = NULL; foreach ($placeholder_order as $placeholder_id) { if (!isset($placeholders[$placeholder_id])) { continue; } // Render the placeholder. $placeholder_render_array = $placeholders[$placeholder_id]; try { $elements = $this->renderPlaceholder($placeholder_id, $placeholder_render_array); // Ensure the messages placeholder renders last, the render order of every // other placeholder is safe to change. // @see static::getPlaceholderOrder() if (isset($placeholder_render_array['#lazy_builder']) && $placeholder_render_array['#lazy_builder'][0] === 'Drupal\Core\Render\Element\StatusMessages::renderMessages') { $message_placeholder_id = $placeholder_id; } catch (\Exception $e) { if ($this->configFactory->get('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) { throw $e; $fibers[$placeholder_id] = new \Fiber(fn() => $this->renderPlaceholder($placeholder_id, $placeholder_render_array)); } else { trigger_error($e, E_USER_ERROR); while (count($fibers) > 0) { $iterations = 0; foreach ($fibers as $placeholder_id => $fiber) { // Keep skipping the messages placeholder until it's the only Fiber // remaining. @todo https://www.drupal.org/project/drupal/issues/3379885 if (isset($message_placeholder_id) && $placeholder_id === $message_placeholder_id && count($fibers) > 1) { continue; } try { if (!$fiber->isStarted()) { $fiber->start(); } elseif ($fiber->isSuspended()) { $fiber->resume(); } // If the Fiber hasn't terminated by this point, move onto the next // placeholder, we'll resume this Fiber again when we get back here. if (!$fiber->isTerminated()) { // If we've gone through the placeholders once already, and they're // still not finished, then start to allow code higher up the stack // to get on with something else. if ($iterations) { $fiber = \Fiber::getCurrent(); if ($fiber !== NULL) { $fiber->suspend(); } } continue; } $elements = $fiber->getReturn(); unset($fibers[$placeholder_id]); // Create a new AjaxResponse. $ajax_response = new AjaxResponse(); // JavaScript's querySelector automatically decodes HTML entities in // attributes, so we must decode the entities of the current BigPipe // placeholder ID (which has HTML entities encoded since we use it to find // the placeholders). // placeholder ID (which has HTML entities encoded since we use it to // find the placeholders). $big_pipe_js_placeholder_id = Html::decodeEntities($placeholder_id); $ajax_response->addCommand(new ReplaceCommand(sprintf('[data-big-pipe-placeholder-id="%s"]', $big_pipe_js_placeholder_id), $elements['#markup'])); $ajax_response->setAttachments($elements['#attached']); // Push a fake request with the asset libraries loaded so far and dispatch // KernelEvents::RESPONSE event. This results in the attachments for the // AJAX response being processed by AjaxResponseAttachmentsProcessor and // hence: // Push a fake request with the asset libraries loaded so far and // dispatch KernelEvents::RESPONSE event. This results in the // attachments for the AJAX response being processed by // AjaxResponseAttachmentsProcessor and hence: // - the necessary AJAX commands to load the necessary missing asset // libraries and updated AJAX page state are added to the AJAX response // - the attachments associated with the response are finalized, which // allows us to track the total set of asset libraries sent in the // initial HTML response plus all embedded AJAX responses sent so far. // libraries and updated AJAX page state are added to the AJAX // response // - the attachments associated with the response are finalized, // which allows us to track the total set of asset libraries sent in // the initial HTML response plus all embedded AJAX responses sent so // far. $fake_request->query->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']); try { $ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response); } catch (\Exception $e) { if ($this->configFactory->get('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) { throw $e; } else { trigger_error($e, E_USER_ERROR); continue; } } // Send this embedded AJAX response. $json = $ajax_response->getContent(); $output = <<<EOF Loading @@ -599,12 +617,24 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde $this->sendChunk($output); // Another placeholder was rendered and sent, track the set of asset // libraries sent so far. Any new settings are already sent; we don't need // to track those. // libraries sent so far. Any new settings are already sent; we // don't need to track those. if (isset($ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])) { $cumulative_assets->setAlreadyLoadedLibraries(explode(',', $ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])); } } catch (\Exception $e) { unset($fibers[$placeholder_id]); if ($this->configFactory->get('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) { throw $e; } else { trigger_error($e, E_USER_ERROR); } } } $iterations++; } // Send the stop signal. $this->sendChunk("\n" . static::STOP_SIGNAL . "\n"); Loading Loading @@ -696,7 +726,7 @@ protected function renderPlaceholder($placeholder, array $placeholder_render_arr /** * Gets the BigPipe placeholder order. * * Determines the order in which BigPipe placeholders must be replaced. * Determines the order in which BigPipe placeholders are executed. * * @param string $html * HTML markup. Loading @@ -705,10 +735,14 @@ protected function renderPlaceholder($placeholder, array $placeholder_render_arr * placeholder IDs. * * @return array * Indexed array; the order in which the BigPipe placeholders must be sent. * Values are the BigPipe placeholder IDs. Note that only unique * placeholders are kept: if the same placeholder occurs multiple times, we * only keep the first occurrence. * Indexed array; the order in which the BigPipe placeholders will start * execution. Placeholders begin execution in DOM order, except for the * messages placeholder which must always be executed last. Note that due to * the Fibers implementation of BigPipe, although placeholders will start * executing in DOM order, they may finish and render in any order. Values * are the BigPipe placeholder IDs. Note that only unique placeholders are * kept: if the same placeholder occurs multiple times, we only keep the * first occurrence. */ protected function getPlaceholderOrder($html, $placeholders) { $placeholder_ids = []; Loading core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php +61 −3 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ * @file */ // cSpell:ignore Vxezb // cSpell:ignore divpiggydiv namespace Drupal\big_pipe_test; Loading Loading @@ -188,7 +189,63 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf ]; $hello->embeddedHtmlResponse = '<marquee>Yarhar llamas forever!</marquee>'; // 5. Edge case: non-#lazy_builder placeholder. // 5. Edge case: non-#lazy_builder placeholder that calls Fiber::suspend(). $piggy = new BigPipePlaceholderTestCase( [ '#markup' => BigPipeMarkup::create('<div>piggy</div>'), '#attached' => [ 'placeholders' => [ '<div>piggy</div>' => [ '#pre_render' => [ '\Drupal\big_pipe_test\BigPipeTestController::piggy', ], ], ], ], ], '<div>piggy</div>', [] ); $piggy->bigPipePlaceholderId = 'divpiggydiv'; $piggy->bigPipePlaceholderRenderArray = [ '#prefix' => '<span data-big-pipe-placeholder-id="divpiggydiv">', 'interface_preview' => [], '#suffix' => '</span>', '#cache' => $cacheability_depends_on_session_and_nojs_cookie, '#attached' => [ 'library' => ['big_pipe/big_pipe'], 'drupalSettings' => [ 'bigPipePlaceholderIds' => [ 'divpiggydiv' => TRUE, ], ], 'big_pipe_placeholders' => [ 'divpiggydiv' => $piggy->placeholderRenderArray, ], ], ]; $piggy->embeddedAjaxResponseCommands = [ [ 'command' => 'insert', 'method' => 'replaceWith', 'selector' => '[data-big-pipe-placeholder-id="divpiggydiv"]', 'data' => '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>', 'settings' => NULL, ], ]; $piggy->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="divpiggydiv></span>'; $piggy->bigPipeNoJsPlaceholderRenderArray = [ '#markup' => '<span data-big-pipe-nojs-placeholder-id="divpiggydiv"></span>', '#cache' => $cacheability_depends_on_session_and_nojs_cookie, '#attached' => [ 'big_pipe_nojs_placeholders' => [ '<span data-big-pipe-nojs-placeholder-id="divpiggydiv"></span>' => $piggy->placeholderRenderArray, ], ], ]; $piggy->embeddedHtmlResponse = '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>'; // 6. Edge case: non-#lazy_builder placeholder. $current_time = new BigPipePlaceholderTestCase( [ '#markup' => BigPipeMarkup::create('<time>CURRENT TIME</time>'), Loading Loading @@ -249,7 +306,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf ]; $current_time->embeddedHtmlResponse = '<time datetime="1991-03-14"></time>'; // 6. Edge case: #lazy_builder that throws an exception. // 7. Edge case: #lazy_builder that throws an exception. $exception = new BigPipePlaceholderTestCase( [ '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']], Loading Loading @@ -297,7 +354,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf // cSpell:disable-next-line. $token = 'PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU'; // 7. Edge case: response filter throwing an exception for this placeholder. // 8. Edge case: response filter throwing an exception for this placeholder. $embedded_response_exception = new BigPipePlaceholderTestCase( [ '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []], Loading Loading @@ -348,6 +405,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf 'html_attribute_value' => $form_action, 'html_attribute_value_subset' => $csrf_token, 'edge_case__invalid_html' => $hello, 'edge_case__html_non_lazy_builder_suspend' => $piggy, 'edge_case__html_non_lazy_builder' => $current_time, 'exception__lazy_builder' => $exception, 'exception__embedded_response' => $embedded_response_exception, Loading core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php +25 −4 Original line number Diff line number Diff line Loading @@ -39,13 +39,16 @@ public function test() { // happens to not be valid HTML. $build['edge_case__invalid_html'] = $cases['edge_case__invalid_html']->renderArray; // 5. Edge case: non-#lazy_builder placeholder. // 5. Edge case: non-#lazy_builder placeholder that suspends. $build['edge_case__html_non_lazy_builder_suspend'] = $cases['edge_case__html_non_lazy_builder_suspend']->renderArray; // 6. Edge case: non-#lazy_builder placeholder. $build['edge_case__html_non_lazy_builder'] = $cases['edge_case__html_non_lazy_builder']->renderArray; // 6. Exception: #lazy_builder that throws an exception. // 7. Exception: #lazy_builder that throws an exception. $build['exception__lazy_builder'] = $cases['exception__lazy_builder']->renderArray; // 7. Exception: placeholder that causes response filter to throw exception. // 8. Exception: placeholder that causes response filter to throw exception. $build['exception__embedded_response'] = $cases['exception__embedded_response']->renderArray; return $build; Loading Loading @@ -127,6 +130,24 @@ public static function currentTime() { ]; } /** * #lazy_builder callback; suspends its own execution then returns markup. * * @return array */ public static function piggy(): array { // Immediately call Fiber::suspend(), so that other placeholders are // executed next. When this is resumed, it will immediately return the // render array. if (\Fiber::getCurrent() !== NULL) { \Fiber::suspend(); } return [ '#markup' => '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>', '#cache' => ['max-age' => 0], ]; } /** * #lazy_builder callback; says "hello" or "yarhar". * Loading Loading @@ -193,7 +214,7 @@ public static function counter() { * {@inheritdoc} */ public static function trustedCallbacks() { return ['currentTime', 'helloOrYarhar', 'exception', 'responseException', 'counter']; return ['currentTime', 'piggy', 'helloOrYarhar', 'exception', 'responseException', 'counter']; } } core/modules/big_pipe/tests/src/Functional/BigPipeTest.php +7 −2 Original line number Diff line number Diff line Loading @@ -170,14 +170,19 @@ public function testBigPipe() { ]); $this->assertBigPipePlaceholders([ $cases['html']->bigPipePlaceholderId => Json::encode($cases['html']->embeddedAjaxResponseCommands), $cases['edge_case__html_non_lazy_builder_suspend']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder_suspend']->embeddedAjaxResponseCommands), $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder']->embeddedAjaxResponseCommands), $cases['exception__lazy_builder']->bigPipePlaceholderId => NULL, $cases['exception__embedded_response']->bigPipePlaceholderId => NULL, ], [ 0 => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId, // The suspended placeholder is replaced after the non-suspended // placeholder even though it appears first in the page. // @see Drupal\big_pipe\Render\BigPipe\Render::sendPlaceholders() 1 => $cases['edge_case__html_non_lazy_builder_suspend']->bigPipePlaceholderId, // The 'html' case contains the 'status messages' placeholder, which is // always rendered last. 1 => $cases['html']->bigPipePlaceholderId, 2 => $cases['html']->bigPipePlaceholderId, ]); $this->assertSession()->responseContains('</body>'); Loading Loading
core/lib/Drupal/Core/Theme/Registry.php +34 −10 Original line number Diff line number Diff line Loading @@ -237,22 +237,46 @@ protected function init($theme_name = NULL) { */ public function get() { $this->init($this->themeName); if (isset($this->registry[$this->theme->getName()])) { return $this->registry[$this->theme->getName()]; if ($cached = $this->cacheGet()) { return $cached; } // If called from inside a Fiber, suspend it, this may allow another code // path to begin an asynchronous operation before we do the CPU-intensive // task of building the theme registry. if (\Fiber::getCurrent() !== NULL) { \Fiber::suspend(); // When the Fiber is resumed, check the cache again since it may have been // built in the meantime, either in this process or via a different // request altogether. if ($cached = $this->cacheGet()) { return $cached; } if ($cache = $this->cache->get('theme_registry:' . $this->theme->getName())) { $this->registry[$this->theme->getName()] = $cache->data; } else { $this->build(); // Only persist it if all modules are loaded to ensure it is complete. if ($this->moduleHandler->isLoaded()) { $this->setCache(); } } return $this->registry[$this->theme->getName()]; } /** * Gets the theme registry cache. * * @return array|null */ protected function cacheGet(): ?array { $theme_name = $this->theme->getName(); if (isset($this->registry[$theme_name])) { return $this->registry[$theme_name]; } elseif ($cache = $this->cache->get('theme_registry:' . $theme_name)) { $this->registry[$theme_name] = $cache->data; return $this->registry[$theme_name]; } return NULL; } /** * Returns the incomplete, runtime theme registry. * Loading
core/modules/big_pipe/src/Render/BigPipe.php +96 −62 Original line number Diff line number Diff line Loading @@ -536,59 +536,77 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde $fake_request = $this->requestStack->getMainRequest()->duplicate(); $fake_request->headers->set('Accept', 'application/vnd.drupal-ajax'); // Create a Fiber for each placeholder. $fibers = []; $message_placeholder_id = NULL; foreach ($placeholder_order as $placeholder_id) { if (!isset($placeholders[$placeholder_id])) { continue; } // Render the placeholder. $placeholder_render_array = $placeholders[$placeholder_id]; try { $elements = $this->renderPlaceholder($placeholder_id, $placeholder_render_array); // Ensure the messages placeholder renders last, the render order of every // other placeholder is safe to change. // @see static::getPlaceholderOrder() if (isset($placeholder_render_array['#lazy_builder']) && $placeholder_render_array['#lazy_builder'][0] === 'Drupal\Core\Render\Element\StatusMessages::renderMessages') { $message_placeholder_id = $placeholder_id; } catch (\Exception $e) { if ($this->configFactory->get('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) { throw $e; $fibers[$placeholder_id] = new \Fiber(fn() => $this->renderPlaceholder($placeholder_id, $placeholder_render_array)); } else { trigger_error($e, E_USER_ERROR); while (count($fibers) > 0) { $iterations = 0; foreach ($fibers as $placeholder_id => $fiber) { // Keep skipping the messages placeholder until it's the only Fiber // remaining. @todo https://www.drupal.org/project/drupal/issues/3379885 if (isset($message_placeholder_id) && $placeholder_id === $message_placeholder_id && count($fibers) > 1) { continue; } try { if (!$fiber->isStarted()) { $fiber->start(); } elseif ($fiber->isSuspended()) { $fiber->resume(); } // If the Fiber hasn't terminated by this point, move onto the next // placeholder, we'll resume this Fiber again when we get back here. if (!$fiber->isTerminated()) { // If we've gone through the placeholders once already, and they're // still not finished, then start to allow code higher up the stack // to get on with something else. if ($iterations) { $fiber = \Fiber::getCurrent(); if ($fiber !== NULL) { $fiber->suspend(); } } continue; } $elements = $fiber->getReturn(); unset($fibers[$placeholder_id]); // Create a new AjaxResponse. $ajax_response = new AjaxResponse(); // JavaScript's querySelector automatically decodes HTML entities in // attributes, so we must decode the entities of the current BigPipe // placeholder ID (which has HTML entities encoded since we use it to find // the placeholders). // placeholder ID (which has HTML entities encoded since we use it to // find the placeholders). $big_pipe_js_placeholder_id = Html::decodeEntities($placeholder_id); $ajax_response->addCommand(new ReplaceCommand(sprintf('[data-big-pipe-placeholder-id="%s"]', $big_pipe_js_placeholder_id), $elements['#markup'])); $ajax_response->setAttachments($elements['#attached']); // Push a fake request with the asset libraries loaded so far and dispatch // KernelEvents::RESPONSE event. This results in the attachments for the // AJAX response being processed by AjaxResponseAttachmentsProcessor and // hence: // Push a fake request with the asset libraries loaded so far and // dispatch KernelEvents::RESPONSE event. This results in the // attachments for the AJAX response being processed by // AjaxResponseAttachmentsProcessor and hence: // - the necessary AJAX commands to load the necessary missing asset // libraries and updated AJAX page state are added to the AJAX response // - the attachments associated with the response are finalized, which // allows us to track the total set of asset libraries sent in the // initial HTML response plus all embedded AJAX responses sent so far. // libraries and updated AJAX page state are added to the AJAX // response // - the attachments associated with the response are finalized, // which allows us to track the total set of asset libraries sent in // the initial HTML response plus all embedded AJAX responses sent so // far. $fake_request->query->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']); try { $ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response); } catch (\Exception $e) { if ($this->configFactory->get('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) { throw $e; } else { trigger_error($e, E_USER_ERROR); continue; } } // Send this embedded AJAX response. $json = $ajax_response->getContent(); $output = <<<EOF Loading @@ -599,12 +617,24 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde $this->sendChunk($output); // Another placeholder was rendered and sent, track the set of asset // libraries sent so far. Any new settings are already sent; we don't need // to track those. // libraries sent so far. Any new settings are already sent; we // don't need to track those. if (isset($ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])) { $cumulative_assets->setAlreadyLoadedLibraries(explode(',', $ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])); } } catch (\Exception $e) { unset($fibers[$placeholder_id]); if ($this->configFactory->get('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) { throw $e; } else { trigger_error($e, E_USER_ERROR); } } } $iterations++; } // Send the stop signal. $this->sendChunk("\n" . static::STOP_SIGNAL . "\n"); Loading Loading @@ -696,7 +726,7 @@ protected function renderPlaceholder($placeholder, array $placeholder_render_arr /** * Gets the BigPipe placeholder order. * * Determines the order in which BigPipe placeholders must be replaced. * Determines the order in which BigPipe placeholders are executed. * * @param string $html * HTML markup. Loading @@ -705,10 +735,14 @@ protected function renderPlaceholder($placeholder, array $placeholder_render_arr * placeholder IDs. * * @return array * Indexed array; the order in which the BigPipe placeholders must be sent. * Values are the BigPipe placeholder IDs. Note that only unique * placeholders are kept: if the same placeholder occurs multiple times, we * only keep the first occurrence. * Indexed array; the order in which the BigPipe placeholders will start * execution. Placeholders begin execution in DOM order, except for the * messages placeholder which must always be executed last. Note that due to * the Fibers implementation of BigPipe, although placeholders will start * executing in DOM order, they may finish and render in any order. Values * are the BigPipe placeholder IDs. Note that only unique placeholders are * kept: if the same placeholder occurs multiple times, we only keep the * first occurrence. */ protected function getPlaceholderOrder($html, $placeholders) { $placeholder_ids = []; Loading
core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php +61 −3 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ * @file */ // cSpell:ignore Vxezb // cSpell:ignore divpiggydiv namespace Drupal\big_pipe_test; Loading Loading @@ -188,7 +189,63 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf ]; $hello->embeddedHtmlResponse = '<marquee>Yarhar llamas forever!</marquee>'; // 5. Edge case: non-#lazy_builder placeholder. // 5. Edge case: non-#lazy_builder placeholder that calls Fiber::suspend(). $piggy = new BigPipePlaceholderTestCase( [ '#markup' => BigPipeMarkup::create('<div>piggy</div>'), '#attached' => [ 'placeholders' => [ '<div>piggy</div>' => [ '#pre_render' => [ '\Drupal\big_pipe_test\BigPipeTestController::piggy', ], ], ], ], ], '<div>piggy</div>', [] ); $piggy->bigPipePlaceholderId = 'divpiggydiv'; $piggy->bigPipePlaceholderRenderArray = [ '#prefix' => '<span data-big-pipe-placeholder-id="divpiggydiv">', 'interface_preview' => [], '#suffix' => '</span>', '#cache' => $cacheability_depends_on_session_and_nojs_cookie, '#attached' => [ 'library' => ['big_pipe/big_pipe'], 'drupalSettings' => [ 'bigPipePlaceholderIds' => [ 'divpiggydiv' => TRUE, ], ], 'big_pipe_placeholders' => [ 'divpiggydiv' => $piggy->placeholderRenderArray, ], ], ]; $piggy->embeddedAjaxResponseCommands = [ [ 'command' => 'insert', 'method' => 'replaceWith', 'selector' => '[data-big-pipe-placeholder-id="divpiggydiv"]', 'data' => '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>', 'settings' => NULL, ], ]; $piggy->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="divpiggydiv></span>'; $piggy->bigPipeNoJsPlaceholderRenderArray = [ '#markup' => '<span data-big-pipe-nojs-placeholder-id="divpiggydiv"></span>', '#cache' => $cacheability_depends_on_session_and_nojs_cookie, '#attached' => [ 'big_pipe_nojs_placeholders' => [ '<span data-big-pipe-nojs-placeholder-id="divpiggydiv"></span>' => $piggy->placeholderRenderArray, ], ], ]; $piggy->embeddedHtmlResponse = '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>'; // 6. Edge case: non-#lazy_builder placeholder. $current_time = new BigPipePlaceholderTestCase( [ '#markup' => BigPipeMarkup::create('<time>CURRENT TIME</time>'), Loading Loading @@ -249,7 +306,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf ]; $current_time->embeddedHtmlResponse = '<time datetime="1991-03-14"></time>'; // 6. Edge case: #lazy_builder that throws an exception. // 7. Edge case: #lazy_builder that throws an exception. $exception = new BigPipePlaceholderTestCase( [ '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']], Loading Loading @@ -297,7 +354,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf // cSpell:disable-next-line. $token = 'PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU'; // 7. Edge case: response filter throwing an exception for this placeholder. // 8. Edge case: response filter throwing an exception for this placeholder. $embedded_response_exception = new BigPipePlaceholderTestCase( [ '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []], Loading Loading @@ -348,6 +405,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf 'html_attribute_value' => $form_action, 'html_attribute_value_subset' => $csrf_token, 'edge_case__invalid_html' => $hello, 'edge_case__html_non_lazy_builder_suspend' => $piggy, 'edge_case__html_non_lazy_builder' => $current_time, 'exception__lazy_builder' => $exception, 'exception__embedded_response' => $embedded_response_exception, Loading
core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php +25 −4 Original line number Diff line number Diff line Loading @@ -39,13 +39,16 @@ public function test() { // happens to not be valid HTML. $build['edge_case__invalid_html'] = $cases['edge_case__invalid_html']->renderArray; // 5. Edge case: non-#lazy_builder placeholder. // 5. Edge case: non-#lazy_builder placeholder that suspends. $build['edge_case__html_non_lazy_builder_suspend'] = $cases['edge_case__html_non_lazy_builder_suspend']->renderArray; // 6. Edge case: non-#lazy_builder placeholder. $build['edge_case__html_non_lazy_builder'] = $cases['edge_case__html_non_lazy_builder']->renderArray; // 6. Exception: #lazy_builder that throws an exception. // 7. Exception: #lazy_builder that throws an exception. $build['exception__lazy_builder'] = $cases['exception__lazy_builder']->renderArray; // 7. Exception: placeholder that causes response filter to throw exception. // 8. Exception: placeholder that causes response filter to throw exception. $build['exception__embedded_response'] = $cases['exception__embedded_response']->renderArray; return $build; Loading Loading @@ -127,6 +130,24 @@ public static function currentTime() { ]; } /** * #lazy_builder callback; suspends its own execution then returns markup. * * @return array */ public static function piggy(): array { // Immediately call Fiber::suspend(), so that other placeholders are // executed next. When this is resumed, it will immediately return the // render array. if (\Fiber::getCurrent() !== NULL) { \Fiber::suspend(); } return [ '#markup' => '<span>This 🐷 little 🐽 piggy 🐖 stayed 🐽 at 🐷 home.</span>', '#cache' => ['max-age' => 0], ]; } /** * #lazy_builder callback; says "hello" or "yarhar". * Loading Loading @@ -193,7 +214,7 @@ public static function counter() { * {@inheritdoc} */ public static function trustedCallbacks() { return ['currentTime', 'helloOrYarhar', 'exception', 'responseException', 'counter']; return ['currentTime', 'piggy', 'helloOrYarhar', 'exception', 'responseException', 'counter']; } }
core/modules/big_pipe/tests/src/Functional/BigPipeTest.php +7 −2 Original line number Diff line number Diff line Loading @@ -170,14 +170,19 @@ public function testBigPipe() { ]); $this->assertBigPipePlaceholders([ $cases['html']->bigPipePlaceholderId => Json::encode($cases['html']->embeddedAjaxResponseCommands), $cases['edge_case__html_non_lazy_builder_suspend']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder_suspend']->embeddedAjaxResponseCommands), $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder']->embeddedAjaxResponseCommands), $cases['exception__lazy_builder']->bigPipePlaceholderId => NULL, $cases['exception__embedded_response']->bigPipePlaceholderId => NULL, ], [ 0 => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId, // The suspended placeholder is replaced after the non-suspended // placeholder even though it appears first in the page. // @see Drupal\big_pipe\Render\BigPipe\Render::sendPlaceholders() 1 => $cases['edge_case__html_non_lazy_builder_suspend']->bigPipePlaceholderId, // The 'html' case contains the 'status messages' placeholder, which is // always rendered last. 1 => $cases['html']->bigPipePlaceholderId, 2 => $cases['html']->bigPipePlaceholderId, ]); $this->assertSession()->responseContains('</body>'); Loading