You are here

protected function BigPipe::sendNoJsPlaceholders in Drupal 10

Same name and namespace in other branches
  1. 8 core/modules/big_pipe/src/Render/BigPipe.php \Drupal\big_pipe\Render\BigPipe::sendNoJsPlaceholders()
  2. 9 core/modules/big_pipe/src/Render/BigPipe.php \Drupal\big_pipe\Render\BigPipe::sendNoJsPlaceholders()

Sends no-JS BigPipe placeholders' replacements as embedded HTML responses.

Parameters

string $html: HTML markup.

array $no_js_placeholders: Associative array; the no-JS BigPipe placeholders. Keys are the BigPipe selectors.

\Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets: The cumulative assets sent so far; to be updated while rendering no-JS BigPipe placeholders.

Throws

\Exception If an exception is thrown during the rendering of a placeholder, it is caught to allow the other placeholders to still be replaced. But when error logging is configured to be verbose, the exception is rethrown to simplify debugging.

File

core/modules/big_pipe/src/Render/BigPipe.php, line 390

Class

BigPipe
Service for sending an HTML response in chunks (to get faster page loads).

Namespace

Drupal\big_pipe\Render

Code

protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) {

  // Split the HTML on every no-JS placeholder string.
  $placeholder_strings = array_keys($no_js_placeholders);
  $fragments = static::splitHtmlOnPlaceholders($html, $placeholder_strings);

  // Determine how many occurrences there are of each no-JS placeholder.
  $placeholder_occurrences = array_count_values(array_intersect($fragments, $placeholder_strings));

  // Set up a variable to store the content of placeholders that have multiple
  // occurrences.
  $multi_occurrence_placeholders_content = [];
  foreach ($fragments as $fragment) {

    // If the fragment isn't one of the no-JS placeholders, it is the HTML in
    // between placeholders and it must be printed & flushed immediately. The
    // rest of the logic in the loop handles the placeholders.
    if (!isset($no_js_placeholders[$fragment])) {
      $this
        ->sendChunk($fragment);
      continue;
    }

    // If there are multiple occurrences of this particular placeholder, and
    // this is the second occurrence, we can skip all calculations and just
    // send the same content.
    if ($placeholder_occurrences[$fragment] > 1 && isset($multi_occurrence_placeholders_content[$fragment])) {
      $this
        ->sendChunk($multi_occurrence_placeholders_content[$fragment]);
      continue;
    }
    $placeholder = $fragment;
    assert(isset($no_js_placeholders[$placeholder]));
    $token = Crypt::randomBytesBase64(55);

    // Render the placeholder, but include the cumulative settings assets, so
    // we can calculate the overall settings for the entire page.
    $placeholder_plus_cumulative_settings = [
      'placeholder' => $no_js_placeholders[$placeholder],
      'cumulative_settings_' . $token => [
        '#attached' => [
          'drupalSettings' => $cumulative_assets
            ->getSettings(),
        ],
      ],
    ];
    try {
      $elements = $this
        ->renderPlaceholder($placeholder, $placeholder_plus_cumulative_settings);
    } 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;
      }
    }

    // Create a new HtmlResponse. Ensure the CSS and (non-bottom) JS is sent
    // before the HTML they're associated with. In other words: ensure the
    // critical assets for this placeholder's markup are loaded first.
    // @see \Drupal\Core\Render\HtmlResponseSubscriber
    // @see template_preprocess_html()
    $css_placeholder = '<nojs-bigpipe-placeholder-styles-placeholder token="' . $token . '">';
    $js_placeholder = '<nojs-bigpipe-placeholder-scripts-placeholder token="' . $token . '">';
    $elements['#markup'] = BigPipeMarkup::create($css_placeholder . $js_placeholder . (string) $elements['#markup']);
    $elements['#attached']['html_response_attachment_placeholders']['styles'] = $css_placeholder;
    $elements['#attached']['html_response_attachment_placeholders']['scripts'] = $js_placeholder;
    $html_response = new HtmlResponse();
    $html_response
      ->setContent($elements);
    $html_response
      ->getCacheableMetadata()
      ->setCacheMaxAge(0);

    // Push a fake request with the asset libraries loaded so far and dispatch
    // KernelEvents::RESPONSE event. This results in the attachments for the
    // HTML response being processed by HtmlResponseAttachmentsProcessor and
    // hence:
    // - the HTML to load the CSS can be rendered.
    // - the HTML to load the JS (at the top) can be rendered.
    $fake_request = $this->requestStack
      ->getMainRequest()
      ->duplicate();
    $fake_request->request
      ->set('ajax_page_state', [
      'libraries' => implode(',', $cumulative_assets
        ->getAlreadyLoadedLibraries()),
    ]);
    try {
      $html_response = $this
        ->filterEmbeddedResponse($fake_request, $html_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 HTML response.
    $this
      ->sendChunk($html_response);

    // Another placeholder was rendered and sent, track the set of asset
    // libraries sent so far. Any new settings also need to be tracked, so
    // they can be sent in ::sendPreBody().
    $cumulative_assets
      ->setAlreadyLoadedLibraries(array_merge($cumulative_assets
      ->getAlreadyLoadedLibraries(), $html_response
      ->getAttachments()['library']));
    $cumulative_assets
      ->setSettings($html_response
      ->getAttachments()['drupalSettings']);

    // If there are multiple occurrences of this particular placeholder, track
    // the content that was sent, so we can skip all calculations for the next
    // occurrence.
    if ($placeholder_occurrences[$fragment] > 1) {
      $multi_occurrence_placeholders_content[$fragment] = $html_response
        ->getContent();
    }
  }
}