Problem/Motivation
#1305882: drupal_html_id() considered harmful; remove ajax_html_ids to use GET (not POST) AJAX requests fixed some important problems around managing HTML ID uniqueness by appending a random suffix. The random suffix means that the ID should not be used in JS and CSS targeting. Instead, that patch created a data-drupal-selector attribute to be used in JS and CSS selectors in lieu of IDs. That issue's change record says:
For forms there is an automatically added additional attribute: data-drupal-selector corresponding to the element id before randomization.
The problem is that this turns out to not be true for the <form> itself. It also turns out to not be true for any form element whose #id is already explicitly set to something returned by Html::getUniqueId(), which is what code is supposed to use when explicitly setting an element's #id.
The reason is that FormBuilder::doBuildForm() has this code:
if (!isset($element['#id'])) {
...
}
else {
$element['#attributes']['data-drupal-selector'] = Html::getId($element['#id']);
}
Which means any element with an already random-suffixed #id from Html::getUniqueId() receives a correspondingly random-suffixed data-drupal-selector. And that includes the <form> itself, since FormBuilder::prepareForm() initializes $form['#id'] to a random-suffixed value.
Proposed resolution
- Fix FormBuilder to never base
data-drupal-selectoron an already randomized ID. Instead, it should always be based on the non-random information from which the ID is derived. - While we're at it, make sure we never clobber an already existing
data-drupal-selectorvalue. This allows code that explicitly sets a form or element #id to also optionally set thedata-drupal-selectorfrom the same pre-randomized information. Note that code that explicitly sets #id is not required to also setdata-drupal-selector. Those two matching (other than the presence/absence of the random suffix) is a developer convenience, not a requirement.
Release notes snippet
In earlier releases, the data-drupal-selector attribute on form elements was unintentionally randomized. Developers who use this attribute in CSS or Javascript selectors should review the change notice for how the automatic generation of the data-drupal-selector attribute has changed.
| Comment | File | Size | Author |
|---|---|---|---|
| #77 | drupal-2897377-MR14060-77.patch | 8.98 KB | anybody |
| #55 | interdiff-53-55.patch | 707 bytes | nod_ |
| #55 | core-2897377-55.patch | 9.46 KB | nod_ |
| #53 | 2897377-53.patch | 8.59 KB | anchal_gupta |
| #53 | interdiff-2897377-52_53.txt | 1.6 KB | anchal_gupta |
Issue fork drupal-2897377
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
effulgentsia commentedComment #4
effulgentsia commentedComment #5
effulgentsia commentedComment #6
effulgentsia commentedClarifying issue title and summary.
Comment #7
wim leersI think this can only be RTBC'd by fellow Form API maintainer Tim Plunkett?
Comment #8
tim.plunkettWow, great find. And the fix looks good.
This should be testable.
Comment #13
Ice-D commentedThank you, the patch in #5 fixed my issue.
(I tried using AjaxFormHelperTrait which didn't work correctly because it uses data-drupal-selector to reload the form. Might be something else to look into.)
Comment #14
krzysztof domańskiThe patch in #5 fails with the latest version of Drupal.
Comment #15
manuel garcia commentedHad a look at this, and to me it looks like the failure is catching a bug introduced by the current patch.
I installed the
ajax_forms_testmodule and had a look at the form element attributes. The problem is that with the patch applied, it is gettingdata-drupal-selector="edit-datetime"on both the date and the time elements, when it should be gettingedit-datetime-dateandedit-datetime-timerespectively.Comment #17
chris burge commentedThanks to everyone who has worked on this. I dug into #14 and #15. I don't think we want to check if the 'data-drupal-selector' attribute has already been set on an element. It breaks with complex fields, such as datetime. When the datetime date and datetime time subfields are being built by
\Drupal\Core\Form\FormBuilder::doBuildForm(), they already have a 'data-drupal-selector' attribute from their parent. We do want to overwrite that. Proposed change:FYI - My use case is validating section configuration forms in Layout Builder. I can trigger an error, but it doesn't appear because of the bug identified by this issue.
Comment #19
chris burge commentedI'm queuing up #5 to re-test on PHP 7.1 & MySQL 5.7 against D8.9.
Comment #20
chris burge commentedComment #21
chris burge commentedRe the "Needs tests" tag, take a look at the test results from #5 and #17. There's test coverage in place.
Comment #22
chris burge commentedChanging to version 8.8.x-dev as this is a non-disruptive bug fix.
Comment #24
chris burge commentedTests still pass on 8.9.x and 9.1.x. Leaving at 'Needs Review'.
Comment #25
tim.plunkettThere is indeed existing test coverage of this code, but it is insufficient.
What is needed is a test that enforces the new change. One that passes with the above fix, but fails when run against HEAD without the fix.
This proves that the bug fix works, and will prevent regressions in the future.
Comment #26
chris burge commentedI have Monday blocked off to work on this. As I've dug further into this issue, I've found additional code that needs addressed. For example, in
\Drupal\layout_builder\Form\ConfigureBlockFormBase::doBuildForm(), the bug described by this issue is acknowledged with a workaround implemented.Comment #27
chris burge commentedI've taken a deeper dive into this issue, and I think it may be as simple as this: If a form's 'data-drupal-selector' attribute is set, then leave it alone. If it's not set, then set it.
In
\Drupal\Core\Form\FormBuilder::prepareForm(), check if 'data-drupal-selector' is set. If yes, then leave it alone. If no, then set it.In
\Drupal\Core\Form\FormBuilder::doBuildForm, don't attempt to set the 'data-drupal-selector' attribute. This has already been handled in::prepareForm().I've removed the following code from the updated patch because, so far as I can tell, it doesn't do anything:
Unit Tests
The patch adds the following unit test:
Drupal\Tests\Core\Form\FormBuilderTest::testDataDrupalSelector(), along with aTestFormWithDataDrupalSelectorclass.Remove workarounds in Layout Builder and Settings Tray
This bug is acknowledged with a workaround implemented in each Layout Builder and Settings Tray. The workarounds can be removed with this patch.
Layout Builder Workaround
Workaround code in
\Drupal\layout_builder\Form\ConfigureBlockFormBase::doBuildForm():BlockFormMessagesTest results with workaround removed and no patch
BlockFormMessagesTest results with workaround removed and with patch
Settings Tray Workaround
Workaround code in
\Drupal\settings_tray\Block\BlockEntitySettingTrayForm::doBuildForm():SettingsTrayBlockFormTest results with workaround removed and no patch
SettingsTrayBlockFormTest results with workaround removed and with patch
Miscellaneous
In Layout Builder, we have coverage for block form messages but not for section form messages. See
Drupal\Tests\layout_builder\FunctionalJavascript\BlockFormMessagesTest. I'm making a note, so we can add a follow-up issue when this patch is committed.Comment #28
chris burge commentedUpdated patch/interdiff files with corrected path.
Comment #31
chris burge commentedIt looks like the following code does do something. This patch adds is back. This should correct the test failures from #28.
Comment #33
chris burge commentedTests passed. The tests-only patch failed, as expected. Setting back to 'Needs Review'.
Comment #34
nod_you can name your patch
somethingsomthing--FAIL.patchto avoid the testbot putting the issue in "needs work" on fails.I'm not convinced the issue is properly replicated in the tests, then again a little tired so I might have missed it.
Comment #35
chris burge commentedThat's good to know! Somehow I missed that up until now.
There are three tests that provide coverage:
Drupal\Tests\settings_tray\FunctionalJavascript\SettingsTrayBlockFormTest::testValidationMessagesDrupal\Tests\layout_builder\FunctionalJavascript\BlockFormMessagesTest::testValidationMessageDrupal\Tests\Core\Form\FormBuilderTest::testDataDrupalSelectorWith the workarounds removed, both
SettingsTrayBlockFormTest::testValidationMessagesandBlockFormMessagesTest::testValidationMessagewill fail without the patch.FormBuilderTest::testDataDrupalSelectorverifies that the 'data-drupal-selector' attribute is only set if it isn't already set. This directly tests the code change contained herein.I don't find test coverage for Layout Builder section form messages.
If we need additional test coverage, just let me know.
Comment #36
nod_ooh I see now why the changes to settings tray and layout builder were in the test-only patch. Thanks for the explanation.
They do show the issue, so it's not a test specific for this issue, but having the bug does make those tests fail. Don't know how specific we need to be in our tests, would need input from someone more up to date on test case practices.
Comment #37
seanbRerolled for latest 8.9.x.
Comment #38
alexpott#3103812: Layout Builder's ConfigureSectionForm forms do not display validation errors on submit adds yet another work around for this issue to remove.
Comment #39
tim.plunkettComment #40
anmolgoyal74 commentedTried to address this as in #3103812: Layout Builder's ConfigureSectionForm forms do not display validation errors on submit.
Not sure, if this is the right approach.
Comment #42
tim.plunkett@anmolgoyal74 I don't think you needed to change the logic from #37, that was fine. It's just that there's an additional workaround in core now (see the other two removed by the patch).
Comment #43
anmolgoyal74 commented@tim.plunkett. Thanks for the feedback.
If the logic from #37 is correct. then what is the remaining task over here?
Comment #44
alexpott@anmolgoyal74 to remove the workaround added in #3103812: Layout Builder's ConfigureSectionForm forms do not display validation errors on submit like the patch in #37 does in ConfigureBlockFormBase for example.
Comment #45
anmolgoyal74 commentedAh Okay. Got it.
I have remved the work around in this patch.
Do we also need to remove the tests added in #3103812: Layout Builder's ConfigureSectionForm forms do not display validation errors on submit
Comment #46
alexpott@anmolgoyal74 the tests are why we committed #3103812: Layout Builder's ConfigureSectionForm forms do not display validation errors on submit - it's great that they are they and add more proof this fix is correct.
Comment #48
alfaguru commentedIn case anyone else bumps into the same issue, it is worth noting that Commerce has its own workaround for this issue which will need to be overridden / removed if you apply this patch.
It is in src/Plugin/Commerce/InlineForm/InlineFormBase.php line 108 in the current version:
Having tested a few possibilities, the only one I have found thus far that doesn't cause issues with other commerce components is to leave that line intact and add an assignment for the data-drupal-selector attribute:
Having to do this suggests the patch at #45 is flawed in how it handles form elements with ids explicitly set but I haven't had enough time to investigate and find a fix for that.
Comment #50
nod_Comment #51
blainelang commentedJust ran into this issue with 9.4.5 and wondering if there is any update. Would be nice to see this fixed before 10.0 - thanks!
Comment #52
anchal_gupta commentedI rerolled the patch against 10.0x and fixed the custom command error. Please review it
Comment #53
anchal_gupta commentedFixed CS error
Comment #55
nod_Confirmed that the patch in #53 includes all the necessary parts for the patch. So it's the right patch.
Test failure comes from point #2 of the issue summary proposed resolution. Since we're not adding data-drupal-selectors anymore for elements with a "hardcoded" ID, a lot of form elements don't have data-drupal-selectors anymore and we relied on one in the cke5 test.
The change in how many data-drupal-selector are set is probably deserving of a change notice and release note.
Comment #56
rauch commentedThe patch in #37 has the side effect that the data-drupal-selectors attribute of views exposed filters is always "views-exposed-form" and not "views-exposed-form-[view_id]-[display]".
Comment #57
nod_Can't reproduce #56 on the 10.1.x branch with the patch from #55.
FYI: This patch will not be backported to 8.x
Comment #58
berliner commentedWow, I'm surprised I haven't run into this before, as it's such a basic problem for everyone working a lot with ajax-enabled forms.
The patch from #55 still applies to 9.4.8 and fixes the issue.
Comment #59
smustgrave commentedOnly moving back to NW for the change record + release note.
Comment #60
chaseonthewebI've attempted a change notice and release note snippet. Please review.
Comment #61
smustgrave commentedChange record seems clear. I think this may be better suites for 10.2 though as this maybe could be disruptive for existing sites if they were using those selectors (could be wrong).
Comment #62
lauriiiShouldn't we override this attribute only when it isn't already set?
<form>element.Comment #64
anybodyJust ran into this in Homebox 3.0.x development and can confirm the issue still exists. Patch from #55 fixes the issue.
Re #62.1:
Makes sense to me, so the developer is still allowed to override it, if needed for edge cases. Any downside ideas?
One could also discuss this is a "core property" starting with
data-drupal-on the other hand.#62.1:
What exactly should be tested and should it be done in this context?
Comment #65
webflo commentedRe-roll of #55.
Comment #66
webflo commentedComment #69
anybodyThis is required for larger projects like Drupal Commerce and Homebox, so I think this is really important. Should it be set to Major for these contrib dependency reasons?
Also see #3426472: Workaround for core bug #2897377 not needed , causes bug
Comment #70
anybodyWhile this is still an important issue, we've found a heavy regression:
Views exposed filters filter button (& AJAX) are broken in Media library modals, because the hack in media_library.module:
is not called any more!
The reason is that in
class MediaLibraryHooks:the condition
is always FALSE using this patch!
$form['#id'] is NOT starting with
views-exposed-form-media-library-widgetany more, but now it is:
views-exposed-form--hWV0dUC_yXg$form['#attributes']['data-drupal-selector'] is also "
views-exposed-form"AJAX is also entirely broken in these Media Modals!
So anyone using this patch, please be aware that it breaks your media modals!
Comment #71
anybody@nod: Just saw that my case verifies #56
In #57 you wrote you can't reproduce it. See #70 for clear reproduction. Any ideas how we could fix that?
Comment #72
anybodyCurrent test implementation in
TestFormWithDataDrupalSelectoris broken IMHO?Furthermore, we need to ensure that #56 / #70 are covered (expected to fail or we need additional tests)!
Comment #73
anybodyFunny... just saw that the logic was fine until #66 where it was changed. That explains why @nod couldn't reproduce the issue I ran into now with the MR and #66!
@webflo any reasons why you changed the logic in #66 (see the if case!).
So let's start a fresh MR with #55 and then implement #62 on top of that.
Comment #74
anybodyComment #77
anybodyStatic patch from MR attached until this is merged.
Comment #78
anybodyComment #79
smustgrave commentedThis one seemed to break the unit tests.
Comment #80
anybody@smustgrave
What exactly do you mean? I guess they never were green, at least not before my recent changes?
So yes, still NW!