function BigPipe::sendNoJsPlaceholders

Same name and namespace in other branches
  1. 9 core/modules/big_pipe/src/Render/BigPipe.php \Drupal\big_pipe\Render\BigPipe::sendNoJsPlaceholders()
  2. 8.9.x core/modules/big_pipe/src/Render/BigPipe.php \Drupal\big_pipe\Render\BigPipe::sendNoJsPlaceholders()
  3. 10 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.

1 call to BigPipe::sendNoJsPlaceholders()
BigPipe::sendPreBody in core/modules/big_pipe/src/Render/BigPipe.php
Sends everything until just before </body>.

File

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

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->query
            ->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();
        }
    }
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.