Problem/Motivation

Any form, when rendered in a block, does not show any form validation error messages or status messages until after a page refresh.
When the same form is rendered using routing, messages are shown as expected without requiring a page refresh. This behaviour can be replicated in 8.1 and 8.2

Steps to reproduce

In a vanilla Drupal Core install, place the User login form block in the left sidebar. Logout, then attempt to login but deliberately enter the username and/or password incorrectly. The status message does not display.

Refresh the page and the error/validation/status message/s will now show.

Proposed resolution

Ensure that when a form in a block is submitted, any error, validation, and status messages render without requiring a page refresh.

Remaining tasks

TBD

User interface changes

None

API changes

TBD

Data model changes

TBD

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

renukakulkarni created an issue. See original summary.

renukakulkarni’s picture

Component: forms system » block.module
jospratiklive’s picture

HI Renuka,

are you coding something like below :

$form = \Drupal::formBuilder()->getForm(Drupal\user\Form\UserLoginForm::class);

and later simply assigning to variable $variables['foo_form'] = $form;

Juterpillar’s picture

+1

I'm also having this issue on my 8.1.8 site. I have also checked on a vanilla install of Drupal Core (8.1.9-dev) by placing the User login form block in the left sidebar and deliberately entering the username/password incorrectly. The status message does not display until the page is refreshed.

Is it possible that the messages for the page are being collated and rendered before the validation error message/s from forms in blocks have been set? Any advice, patches, or suggestions for where/how to patch would be greatly appreciated.

Juterpillar’s picture

Priority: Normal » Major
Issue summary: View changes

After discussing this issue with other, more prominent contributors to Drupal, I have re-written this issue description using the recommended issue template.

I have also bumped the priority of this issue to 'Major', because this bug is present in Drupal core, and it interferes with normal site visitors' use of the site. There is no current workaround that I am aware of.

Fabianx’s picture

Priority: Major » Critical
Issue summary: View changes

Reproduced on simplytest.me on 8.3.x, too.

I think actually this is a critical UX regression of not seeing a message for the form.

Steps to reproduce again in bullet form:

  • - Login as admin
  • - Place user login block in first sidebar
  • - Logout
  • - Login with invalid credentials
  • - No message shown
Wim Leers’s picture

Ugh, this sounds like a bug in the render system, potentially related to placeholders.

Wim Leers’s picture

By the way, great catch! Thanks for reporting this!

Fabianx’s picture

Issue tags: +Regression

I stay by it being a critical regression and potentially a release blocker.

It amazingly still works in 8.0.x, but fails in 8.1.x and 8.3.x. I did not test 8.2.x.

Berdir’s picture

Yes, see test history on https://www.drupal.org/pift-ci-job/450541, it apparently broke between april and mai. We're currently trying to figure out which change that was exactly.

Fabianx’s picture

I suspect it is:

#2702609: Disable placeholdering (and BigPipe) on unsafe requests to help find forms as fast as possible (to allow EnforcedResponses to work)

because we disable placeholdering, it means that the messages block might be already displayed when the other block is executed.

I think we must only disable auto-placeholdering and/or use a real placeholder for the messages block (e.g. #attached placeholders) so it gets replaced as late as possible in the page request in any case.

Fabianx’s picture

Confirmed, if I place the messages block in the footer, the messages do show up.

Wim Leers’s picture

it means that the messages block might be already displayed when the other block is executed.

… but didn't #2712935: Messages are not rendered last, meaning those messages set within placeholders only appear on the next request fix exactly that?

Can you explain how this somehow still renders messages before rendering other blocks? Because upon re-reading the #2712935: Messages are not rendered last, meaning those messages set within placeholders only appear on the next request patch, that should be impossible AFAICT.

Fabianx’s picture

Status: Active » Needs review
diff --git a/core/lib/Drupal/Core/Render/Element/StatusMessages.php b/core/lib/Drupal/Core/Render/Element/StatusMessages.php
index f026aa5..345fb8b 100644
--- a/core/lib/Drupal/Core/Render/Element/StatusMessages.php
+++ b/core/lib/Drupal/Core/Render/Element/StatusMessages.php
@@ -45,12 +45,13 @@ public function getInfo() {
    *   The updated renderable array containing the placeholder.
    */
   public static function generatePlaceholder(array $element) {
-    $element['messages_placeholder'] = [
+    $element = [
       '#lazy_builder' => [get_class() . '::renderMessages', [$element['#display']]],
-      '#create_placeholder' => TRUE,
     ];
 
-    return $element;
+    // Directly create a placeholder as we need this to be placeholder
+    // regardless if this is a POST or GET request.
+    return \Drupal::service('render_placeholder_generator')->createPlaceholder($element);
   }
 
   /**

Untested, but that should be all that is needed to fix this once and for all.

Messages are just very special.

Fabianx’s picture

#14:

The reason is that we disable _all_ placeholdering for POST requests, so the other fix with the order never even is run.

So suddenly the order of the blocks matter again:

    // If instructed to create a placeholder, and a #lazy_builder callback is
    // present (without such a callback, it would be impossible to replace the
    // placeholder), replace the current element with a placeholder.
    // @todo remove the isMethodSafe() check when
    //       https://www.drupal.org/node/2367555 lands.
    if (isset($elements['#create_placeholder']) && $elements['#create_placeholder'] === TRUE && $this->requestStack->getCurrentRequest()->isMethodSafe()) {
      if (!isset($elements['#lazy_builder'])) {
        throw new \LogicException('When #create_placeholder is set, a #lazy_builder callback must be present as well.');
      }
      $elements = $this->placeholderGenerator->createPlaceholder($elements);
    }

is the relevant snippet.

Maybe we should also just do the opt-out of placeholdering for POST requests for non-explicit placeholders (e.g. auto-placeholders) instead of for all ...

Wim Leers’s picture

Gahhhhhhh. An edge case of an edge case, all because of EnforcedResponses, which we had to do this for in #2702609: Disable placeholdering (and BigPipe) on unsafe requests to help find forms as fast as possible (to allow EnforcedResponses to work).

Juterpillar’s picture

Thanks everyone for escalating this so quickly!

I have attached a patch that uses Fabianx's suggestion. I had to allow render_placeholder_generator to be a public service for this to work. I'm not sure what unintended consequences this may have.

The patch attached appears to fix this issue.

Wim Leers’s picture

Issue tags: +Needs tests

Thanks, @Juterpillar! :)

We definitely still need tests for this. Tests that reproduce this problem. Looking at the steps to reproduce in the issue summary, that should be easy.

Wim Leers’s picture

Status: Needs review » Needs work

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Gold’s picture

Wim,

Julia has left for a 3 week break. Personally, I've not had the opportunity to play with D8 yet but this sounds like a good chance. If I can find the time I'll have a look at writing the tests for this some time this week.

Wim Leers’s picture

Awesome, thanks a lot! I'm happy to provide reviews/tips to help you get this patch to RTBC!

Berdir’s picture

I hope you're not too mad about me stealing away the issue :)

Here are tests using the user login block, where the existing user login block tests are. Not the perfect place to test a regression in the render system, we could also move it somewhere else, but I personally could live with them being there ;)

I've also verified with the failing simplenews tests, they pass again now with the patch applied.

Berdir’s picture

The last submitted patch, 24: form_validation_errors-2773333-24-tests-only.patch, failed testing.

Wim Leers’s picture

Status: Needs review » Needs work

I hope you're not too mad about me stealing away the issue :)

I don't think anyone is :) Thanks for jumping in!

So, basically… this patch is special-casing the messages placeholder even more. It's overriding the default behavior at:

    // If instructed to create a placeholder, and a #lazy_builder callback is
    // present (without such a callback, it would be impossible to replace the
    // placeholder), replace the current element with a placeholder.
    // @todo remove the isMethodSafe() check when
    //       https://www.drupal.org/node/2367555 lands.
    if (isset($elements['#create_placeholder']) && $elements['#create_placeholder'] === TRUE && $this->requestStack->getCurrentRequest()->isMethodSafe()) {
      …
      $elements = $this->placeholderGenerator->createPlaceholder($elements);
    }

… and is instead always calling that service. It's essentially omitting the final condition in the if-test.

In other words, once #2367555: Deprecate EnforcedResponseException support lands, this work-around will also become unnecessary and should be reverted. Hence we should duplicate that same @todo to the changes we're making in StatusMessages and to the fact that we're making a private service public.

Fabianx’s picture

+1 to adding a @todo

Berdir’s picture

The current issue title of that issue implies to me that we can not actually remove this yet, as it just says to deprecate it, we'd still have to support it until it is actually removed? Also, making a service public is fine, but making it private again could break some other code so it would be an API change on its own.

Anyway, added the @todo's, if it doesn't happen in that issue the in the follow-up to actually remove that. And we can decide there about the service, on purpose used "Consider".

Fabianx’s picture

Status: Needs review » Reviewed & tested by the community

RTBC - Nice work!

catch’s picture

Status: Reviewed & tested by the community » Fixed
+++ b/core/lib/Drupal/Core/Render/Element/StatusMessages.php
@@ -45,12 +45,15 @@ public function getInfo() {
+    // Directly create a placeholder as we need this to be placeholder

s/to be placeholder/to be placeholdered/

- fixed on commit to 8.3.x/8.2.x, thanks!

  • catch committed 2317a39 on 8.3.x
    Issue #2773333 by Berdir, Juterpillar, Wim Leers, Fabianx,...

  • catch committed a9f47ff on 8.2.x
    Issue #2773333 by Berdir, Juterpillar, Wim Leers, Fabianx,...
Wim Leers’s picture

#29: thanks for adding that. Even if it doesn't get changed during the 8.x cycle, it should definitely be fixed in 9.x. Having a @todo there is just a valuable reminder, because this sort of change would easily be forgotten otherwise.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

nitin.k’s picture

Great #29 is working perfectly. I was facing this issue from a long time.

Many thanks !!