Problem/Motivation
Every island form uses the form id display_builder_island. When a panel is re-rendered in a later request, its form gets the same HTML id as a form already on the page, because the id counter starts over. After that, the visibility form posts the contextual form's form_build_id.
The form ids match, so FormBuilder loads the contextual form's cached state and builds the wrong form. The component form values are then stored as visibility conditions, and rendering fails:
PluginNotFoundException: The "component" plugin does not exist. in VisibilityConditionsPanel->alterElement()
It only shows once the contextual form is cached, which happens when it has #ajax elements, as with UI Patterns #3622928.
Steps to reproduce
- With UI Patterns #3622928 applied, run
visibility.spec.ts. - Set a Request Path condition: the component is hidden.
- Clear it: the request returns a 500 and the component never comes back.
Proposed resolution
One form id per island: display_builder_island_<island_id>. IslandFormBase takes the island id in its constructor, and its three callers pass a form object instead of the class name.
Remaining tasks
- Review.
Issue fork display_builder-3623715
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
mogtofu33 commentedComment #3
mogtofu33 commentedComment #4
pdureau commentedbeta9 has been released. Changing the tag.
Comment #5
pdureau commentedIs there an overlap with #3623623: Contextual form loses clicks and invalid values silently? Which already gives each island has its own form ID,
display_builder_island_ {island_id}.Commited the 17th of September, so the day after the creation of this ticket: https://git.drupalcode.org/project/display_builder/-/commit/cda4f8336de4...
#3622928: UX of the asynchronous component form has been commited to UI Patterns the same day and
visibility.spec.tsrun fine since.I don't see much to do here, except an additional, and optional, check in
ApiController, comparing the Form IDs. Am I missing something?I can do a MR with the additional check, it may help.
Comment #7
pdureau commentedCheck and related test added by a LLM. I am still not 100% sure we need it.
Comment #8
csakiistvanComment #9
csakiistvan❓ Needs more info — could not reproduce the 500 from the reported steps:
visibility.spec.tspasses on current 1.0.x without MR !515, with UI Patterns 2.0.23 (which includes #3622928). The kernel test added by MR !515 does fail without the fix and passes with it. Which setup still triggers the 500 in the browser?Before / after (kernel test)
Notes
Comment #10
pdureau commentedIndeed, the issue was already fixed by #3623623: Contextual form loses clicks and invalid values silently, this work only add some safeguards.
The review of the current MR is more an expression of personal taste.
Are the precautions added to
ApiControllersomething sensible, something we may need? Are are they a bit too much?Is it something we need to work on?
Comment #11
csakiistvan@pdureau The kernel test only reaches the guard by posting the Visibility form_id together with the Config form's form_build_id, a pair the builder no longer sends since #3623623. I would close this as fixed by #3623623 rather than add the guard; if anything, a test asserting that island forms keep distinct form ids would protect the actual fix.
Comment #12
smovs commentedReviewed MR !515 and ran the kernel tests. Agree with @csakiistvan: the bug is already fixed by #3623623.
validateIslandForm(): I'd drop it, with its test. The builder can't send that request anymore: each island form only posts its ownform_idandform_build_id.try/catchinthirdPartySettingsUpdate(): I'd keep it. Without it, a validation error in the Styles or Visibility form isn't caught. I tested this by posting a role that doesn't exist: it throwsFormValidationExceptioninstead of showing the message.getFormId()goes back to one shared id, 4 tests inContextualFormPanelRequestTestalready fail.Suggestion: keep only the
try/catch, ideally with a small test for it.Comment #13
csakiistvanThanks @smovs, agreed on all three. I called the guard a cheap safety net before, but since the builder can no longer send that request, dropping it with its test is fine with me. Keeping only the
try/catchinthirdPartySettingsUpdate()plus a small test for the invalid-role case sounds like the right scope for this issue. Change to Postponed (maintainer needs more info) status, waiting for the decisionComment #14
csakiistvanComment #15
pdureau commentedOK, i will have a look
Comment #16
pdureau commentedThanks both of you. I am still struggling to have a personal opinion here, but your agreement about "Keeping only the try/catch in thirdPartySettingsUpdate() plus a small test for the invalid-role case" is enough for me and I am blindly following you.
I have amended the commit and a little review will be welcomed.
This commit still has a different purpose than the ticket description (because the original task has already been shipped in #3623623: Contextual form loses clicks and invalid values silently).
Comment #17
csakiistvanComment #18
csakiistvan@pdureau The new
testThirdPartySettingsUpdateWithInvalidRolepassed without the try/catch: the island forms get their values throughFormState::setValues(), so core's "illegal choice" check never runs and an unknown role raises no error. I pushed a commit replacing it withtestThirdPartySettingsUpdateWithValidationError, which forces a validation error through a test hook indisplay_builder_test. It fails without the try/catch (FormValidationException: Test validation error.) and passes with it, as does the fullApiControllerTest. Leaving it in Needs review.Comment #19
pdureau commentedI will review the added commit
Comment #20
pdureau commentedIt may be the
display_builder-1.0.0-betaticket I am less knowledgeable about but the changes look legit.Comment #22
mogtofu33 commented