The component fails to render the library and props forms due to an unhandled runtime exception.

Created on 23 May 2025, 12 days ago

Overview

  • Open a Twig template file, such as my-hero.twig.
  • Modify the following line:
<div {{ attributes }} class="my-hero__container">

to use an invalid filter:

<div {{ attributes|invalidFilter }} class="my-hero__container">

(Note: This intentionally introduces an error to simulate the issue. Any invalid Twig filter will trigger the same behavior.)

  • Save the file and clear the cache.
  • Reload the affected pages in the browser.
  • Observe that the component sections fail to render, resulting in an exception.
  • Proposed resolution

    User interface changes

    ๐Ÿ› Bug report
    Status

    Active

    Version

    0.0

    Component

    Component sources

    Created by

    ๐Ÿ‡ฎ๐Ÿ‡ณIndia nagwani

    Live updates comments and jobs are added and updated live.
    Sign in to follow issues

    Merge Requests

    Comments & Activities

    • Issue created by @nagwani
    • ๐Ÿ‡ฎ๐Ÿ‡ณIndia nagwani
    • ๐Ÿ‡ฎ๐Ÿ‡ณIndia nagwani
    • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

      This is surprising. ๐Ÿ“Œ [PP-1] Failing kernel tests for all ways a component source can fail to render on the server side Active added test coverage for exactly this and ๐Ÿ“Œ Improve server-side error handling Active was supposed to protect against this.

      See tests/modules/xb_test_sdc/components/crash/crash.twig. That is handled correctly. But this evidently is not. So let's at minimum expand our existing test coverage with this, and then go from there.

      Epic find! ๐Ÿ‘

    • First commit to issue fork.
    • ๐Ÿ‡ช๐Ÿ‡ธSpain isholgueras

      I've found the issue. The problem is that the Renderer in Core can throw an exception and we're not handling it in XB.
      It breaks in the ClientSideRepresentation. Here is the diff to explain a bit:

      diff --git a/components/my-hero/my-hero.twig b/components/my-hero/my-hero.twig
      index 5b39e616..ce9c5c02 100644
      --- a/components/my-hero/my-hero.twig
      +++ b/components/my-hero/my-hero.twig
      @@ -1,4 +1,4 @@
      -<div {{ attributes }} class="my-hero__container">
      +<div {{ attributes|invalidFilter }} class="my-hero__container">
         <h1 class="my-hero__heading">{{ heading }}</h1>
         <p class="my-hero__subheading">{{ subheading }}</p>
         <div class="my-hero__actions">
      diff --git a/src/ClientSideRepresentation.php b/src/ClientSideRepresentation.php
      index 4637364f..baa6368f 100644
      --- a/src/ClientSideRepresentation.php
      +++ b/src/ClientSideRepresentation.php
      @@ -11,6 +11,7 @@ use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
       use Drupal\Core\Render\BubbleableMetadata;
       use Drupal\Core\Render\RendererInterface;
       use Drupal\experience_builder\Render\ImportMapResponseAttachmentsProcessor;
      +use Twig\Error\SyntaxError;
       
       /**
        * @see \Drupal\jsonapi\Normalizer\Value\CacheableNormalization
      @@ -49,7 +50,11 @@ final class ClientSideRepresentation implements RefinableCacheableDependencyInte
           }
       
           $build = $this->preview;
      -    $default_markup = $renderer->renderInIsolation($build);
      +    try {
      +      $default_markup = $renderer->renderInIsolation($build);
      +    } catch (SyntaxError $e) {
      +            $default_markup = '<div>Render error: ' . htmlspecialchars($e->getMessage()) . '</div>';
      +    }
           $assets = AttachedAssets::createFromRenderArray($build);
           $import_map = ImportMapResponseAttachmentsProcessor::buildHtmlTagForAttachedImportMaps(BubbleableMetadata::createFromRenderArray($build)) ?? [];
      

      That's why it's throwing an exception and we're not catching it, and with this, the result is... better.

      The problem is that in the Renderer, when it detects the exceptions, it catch it,set it to renderingRoot to false and throw again the exception.

          try {
            return $this->doRender($elements, $is_root_call);
          }
          catch (\Exception $e) {
            // Mark the ::rootRender() call finished due to this exception & re-throw.
            $this->isRenderingRoot = FALSE;
            throw $e;
          }
      
    • ๐Ÿ‡ช๐Ÿ‡ธSpain isholgueras
    • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

      TIL Twig throws \Twig\Error\SyntaxError and Drupal core's renderer service does not catch it ๐Ÿคฏ I didn't know core's abstractions were so leaky ๐Ÿ˜ฌ I'd argue this is a bug in the SDC subsystem in Drupal core ๐Ÿ˜‡ โ†’ adding to ๐Ÿ“Œ [SPIKE] Comprehensive plan for integrating with SDC Active .

      Thanks for the detailed analysis and write-up in #6, that allowed me to review this in ~5 mins instead of >30! ๐Ÿ™๐Ÿ‘

      My question is: Should it be treated as an `IneligibleComponent? My guess is NO, because is an error in runtime. How can we detect an invalid filter (or something else) in the code without rendering it first?

      Great question!

      • Yes, it should be treated as an ineligible component.
      • Yes, we can detect this: \Twig\Parser::parse() would throw it.

      Maybe render with the example values inthe setCachedDefinitions? I guess is too killer.

      That seems very reasonable to me. But AFAICT the above would be sufficient.

      How can we detect an invalid filter (or something else) in the code without rendering it first?

      We can: Parser::parse() ๐Ÿค“

    • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

      Actually, it's both! So keeping as-is. I'd indeed have expected #3517941 to have prevented this, even if the SDC discovery logic should've detected and prevented this. Because it's possible an SDC is modified and then starts failing.

      See https://git.drupalcode.org/project/experience_builder/-/merge_requests/1... for the test coverage that should be updated to prove that \Drupal\experience_builder\Element\RenderSafeComponentContainer::renderComponent() SHOULD catch this and degrade gracefully. We'll need to debug why that's not the case.

    • First commit to issue fork.
    • ๐Ÿ‡ฆ๐Ÿ‡บAustralia larowlan ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ.au GMT+10

      Reworked to re-use the existing test to prove that this bug has nothing to do with what that test is testing, which is the preview component.
      Instead the error is with the ::normalizeForClientSide.
      Added changes to the existing test to demonstrate the bug and then added the fix, which is to use the render safe component container.

    • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

      I'm relieved that the fear/surprise I expressed โ†’ in #10 has been disproven :)

      This IMHO stresses the importance of ๐Ÿ“Œ Evolve the organically grown `::getClientSideInfo()` into something cohesive Active โ€” we shouldn't have to manually use RenderSafeComponentContainer all over the place?

    • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ
    • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

      We could still do the discovery bits suggested by @isholgueras in #6 and confirmed by me in #9.

      But thatโ€™s going very far in babysitting broken code. But most importantly: an SDCโ€™s Twig template could change any second! (In that sense, this is similar to ๐Ÿ’ฌ ComponentNotFoundException: Unable to find component Active .) Then the discovery wouldnโ€™t help. But the RenderSafeComponentContainer will.

      I did express โ†’ in #10 my surprise at RenderSafeComponentContainer not handling this โ€” both @isholgueras and I missed that we simply were not using it in this one spot, but thankfully @larowlan spotted that!

      So this is the simplest possible solution that avoids broken SDCs being able to break XB ๐Ÿ˜Š

    • Pipeline finished with Skipped
      1 day ago
      #513009
    • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ
    Production build 0.71.5 2024