Blocks #606840: Enable internal page cache by default.

This fixes the 13 failures o/t parent issue in:

  • ExpandDrupal\system\Tests\Ajax\ElementValidationTest (4)
  • ExpandDrupal\system\Tests\Ajax\FrameworkTest (9)

This regression was caused by #2242749: Port Form API security fix SA-CORE-2014-002 to Drupal 8. Note how that issue is about fixing AJAX forms when page caching is enabled — but #ajax is not mentioned anywhere in the issue, nor in the patch. The test coverage added in that patch only cares about AJAX-style submitting of entire forms, not about forms that use #ajax callbacks.

Thanks to #606840: Enable internal page cache by default, which wants to enable page caching by default, we found 13 AJAX system test failures. These failures are happening because they're now (well, in that issue) being tested with page caching enabled — thus in fact expanding the test coverage to also check that #ajax works as well when page caching is enabled.


A very compact analysis of the problem + solution:

[27/03/15 17:34:10] Wim Leers: So this line in FormAjaxController (lines 115–116) is a plain lie:

    /** @var \Drupal\Core\Ajax\AjaxResponse $response */
    $response = call_user_func_array($callback, [&$form, &$form_state]);
[27/03/15 17:34:20] Wim Leers: it's NOT guaranteed to be an AJAX response *at all*
[27/03/15 17:34:26] Wim Leers: it's actually almost always a form array
[27/03/15 17:35:13] Tim Plunkett: then how are we getting away with calling methods on it?
[27/03/15 17:35:21] Wim Leers: that's the real kicker
[27/03/15 17:35:29] Wim Leers: NOTHING in HEAD exercises that code path…
[27/03/15 17:35:35] Wim Leers: $commands is always empty
[27/03/15 17:35:40] Tim Plunkett: O_O
[27/03/15 17:35:40] Tim Plunkett: ohhhh
[27/03/15 17:35:43] Wim Leers: therefore we don't call $response->addCommand()
[27/03/15 17:35:47] Wim Leers: so we return $response
[27/03/15 17:35:51] Wim Leers: which is a render array
[27/03/15 17:35:56] Wim Leers: which is then handled later in the render pipeline
[27/03/15 17:36:00] Wim Leers: it's detected to be a render array
[27/03/15 17:36:05] Wim Leers: and then transformed into an AjaxResponse
[27/03/15 17:36:20] Wim Leers: ---
[27/03/15 17:36:29] Wim Leers: And the really fun fact: we can't actually solve this very cleanly.
[27/03/15 17:36:43] Wim Leers: Because the getting of these commands DOES need to happen this early
[27/03/15 17:36:58] Wim Leers: So the only possible solution is to look at the result of the callback, check if it's a response, and if it isn't, turn it into a response
[27/03/15 17:39:21] Wim Leers: that actually makes things work again

This avoids any API breaks.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

Assigned: Wim Leers » Unassigned
Issue summary: View changes
Status: Active » Needs review
FileSize
858 bytes
4.64 KB

tim.plunkett’s picture

  1. +++ b/core/modules/system/src/Controller/FormAjaxController.php
    @@ -112,8 +137,20 @@ public function content(Request $request) {
         foreach ($commands as $command) {
           $response->addCommand($command, TRUE);
         }
    

    You mentioned that we have no real test coverage where $commands is anything other than an empty array. Does the added test exploit that, or something else?
    Nevermind, checked the results of the test-only patch, that's exactly what you're doing :)

  2. +++ b/core/modules/system/src/Tests/Ajax/AjaxFormPageCacheTest.php
    @@ -83,4 +83,14 @@ public function testSimpleAJAXFormValue() {
    +  function testAjaxElementValidation() {
    

    public function :)

This seems RTBC otherwise.

effulgentsia’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
4.46 KB
612 bytes

+1 to #3: RTBC.

A way to test this bug manually is add a multivalued text field to user registrations, enable page caching, and then try to use the "Add another item" button on user/register while logged out.

I thought the existing user picture field would be similarly broken (which I think would have made this issue critical), but no, because it uses one of those rare #ajax callbacks that actually returns a Response object (FileWidgetAjaxController::upload()), and this bug is only triggered by #ajax callbacks that return render arrays (such as WidgetBase::addMoreAjax()).

In any case, the patch includes the appropriate test, so the above is just for those curious about a non-test scenario.

effulgentsia’s picture

Version: 9.x-dev » 8.0.x-dev
Priority: Critical » Major
Status: Active » Reviewed & tested by the community

A ghost corrupted the issue attributes. Restoring.

Fabianx’s picture

RTBC + 1

  • alexpott committed f35996a on 8.0.x
    Issue #2461063 by Wim Leers, effulgentsia: AJAX forms using #ajax broken...
Wim Leers’s picture

Status: Reviewed & tested by the community » Fixed

Was committed, see #7.

Status: Fixed » Closed (fixed)

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