Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
It seems like changes to "Manually editable HTML tags" field are lost if form is submitted without triggering AJAX.
Steps to reproduce
- Create text format using CKEditor 5
- Enable Source plugin
- Enable the "Limit allowed HTML tags and correct faulty HTML" filter
- Add
<div data-foo>
to "Manually editable HTML tags" - Press "Save" without clicking anywhere else on the page
- Visit text format and confirm that "Manually editable HTML tags" field is empty
Proposed resolution
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Comment | File | Size | Author |
---|---|---|---|
#47 | interdiff_43-47.txt | 2.47 KB | bnjmnm |
#47 | 3265626-47.patch | 7.91 KB | bnjmnm |
| |||
#43 | 3265626-43.patch | 7.89 KB | bnjmnm |
| |||
#43 | interdiff_40-43.txt | 3.91 KB | bnjmnm |
#40 | interdiff_37-40.txt | 2.48 KB | bnjmnm |
Comments
Comment #2
Wim LeersComment #3
Wim LeersComment #4
Wim LeersRan into this earlier today. Confirming data loss/confusion! Bad for usability for sure.
Comment #5
andregp CreditAttribution: andregp at CI&T commentedThe step 4 on the IS is really important. I've noticed some things:
1. The issue above also happens when removing tags previously saved and not clicking anywhere else but in the "Save configuration" button.
2. As soon as you click outside the input box right after adding/removing a tag, the box flashes out really fast. I guess it means something happens when click anywhere else (probably the system tracking the modification on the input box), that doesn't happen (or doesn't have enough time to happen) whey you click directly on the Save button right after modifying the tags.
3. I added another textarea right bellow the Manually editable HTML tags just for test, with an empty default value. The flashing mentioned on 2 doesn't happen when typing and clicking outside this.
I'd like to go deeper on this issue, but I'm still learning Drupal and have no clue on how to proceed from this point, so I'll keep following this issue to see if I can learn why this is happening and how to fix it.
Comment #6
Wim LeersI am struggling to write a failing test for this.
It looks like
somehow triggers the AJAX updates anyway? 🤔
Comment #7
bnjmnmComment #9
Wim LeersAha! This is the crucial difference!
Thanks @bnjmnm :)
Comment #10
nod_Struggling with reproducing this. The tests fail as well for me too.
To reproduce locally I have to add a delay to the ajax calls and click twice on the submit button for the problem to show up. A single click doesn't submit the form, just gets the ajax to trigger.
Comment #11
Wim LeersInteresting, @nod_! Which browser? Firefox? I've been testing with Chrome, and that's what DrupalCI tests with.
Maybe Chrome and Firefox process a trigger of the form submit button differently… 🤔
Comment #12
bnjmnmHave not found the cause, but a few things I've narrowed down:
But obviously that breaks the dynamic updating of HTML restrictions
I feel like that narrows it down considerably, but I'm not sure what to yet...
Comment #13
nod_Ok to reproduce the "Limit allowed HTML tags and correct faulty HTML" checkbox needs to be checked.
Comment #14
nod_Thanks ben, that helped me find the issue :)
When the submit button is clicked, the ajax callback is triggered and this piece of code is run on the textarea:
Textarea become disabled and the value is not sent as part of the form on submit. Commenting this line make things work as expected.
Don't know yet why when "Limit allowed HTML tags and correct faulty HTML" is not enabled it behaves as intended (form submission is prevented on first click on the save config button) but when the filter is enabled it just submit the form.
Comment #15
nod_fixes it when manual testing. Doesn't seem to make the test pass though.
I don't particularly like the solution, just the first thing that worked, need polish.
I'm not sure the same thing is going on in the test than manually testing. It doesn't seem to have the ajax call triggered when assigning .value to the textarea and clicking on the button, but if that is the case don't know why it would fail....
Comment #16
nod_Comment #18
nod_Sooo, seems like the no-js version doesn't even save the value properly. I visit the page with JS disabled, add a
<kbd>
element to the Manually editable HTML tags field, save the form… and nothing is added to the Allowed HTML tags textarea.It seems like
_update_ckeditor5_html_filter
does extra work compared to the regular form submit. It seems that$renderedField = $renderer->render($form['editor']['settings']);
is the piece of code responsible for the changes actually propagating.Comment #19
Wim LeersWow, nice sleuthing! Seems like this one is extremely tricky 😬
Comment #20
bnjmnmAttached is an arguably hacky solution, but one that works for me locally (we will see if Drupal CI agrees). Maybe this will guide us to a more robust solution, but at the very least we now know this can be addressed somehow. Explanation of how the fix works are in the code comments.
Comment #21
bnjmnmIn #20 Cspell was objecting to a word in a transpiled js file that removes
cspell: ignore
as part of the transpilation, so the term was added to dictionary.txt.Comment #23
Wim LeersSo … does this mean you gave up on the
\Drupal\Core\Form\SubformStateInterface::getCompleteFormState()
stuff in a submit handler? IIRC you said on Friday you wanted to try that, but #20 doesn't quite look like that? 🤔😅Comment #24
bnjmnmRe #23
Turns out it needs what I added in #21 AND a submit handler. There are two problems being addressed
SourceEditing
)HTMLRestrictions
)Comment #25
bnjmnmComment #26
Wim Leers👍 This one I understand. This corresponds to #24.2.
It's also not the bug originally reported AFAICT, so perhaps it's worth tackling in a separate issue? But let's first verify that this really is a separate bug, because I'm really puzzled on how to reproduce it at all 🙈
So … extracted a subset of #25, that covers the original issue scope and includes the original test coverage.
Comment #28
Wim Leers🤓 I tried removing this form-level submit handler and instead updated
\Drupal\ckeditor5\Plugin\Editor\CKEditor5::getGeneratedAllowedHtmlValue()
. But the end result was A) more confusing, B) could not rely on$form_state->get('ckeditor5_validated_pair')->getFilterFormat();
being available.🙏 So all I think that remains here is adding the missing docblock and perhaps an
@see _update_ckeditor5_html_filter()
.Comment #29
bnjmnmHere's a video with both issues, from HEAD: https://youtu.be/osxqeWtn5Ro
If I update the Source Editing Manually editable HTML Tags field and go directly to save there are two problems
Additional weird (interesting?) thing
I paused the video at 13 seconds, right after "Save configuration" was clicked. For a moment, the Allowed HTML tags is updated.
Comment #30
Wim LeersI bet that's the AJAX request being fired just at the time you are clicking
(because that is what triggers thechange
event), and the AJAX request getting a response faster than the form submission finishes.… which I think points to the root cause of all of this: in HEAD, it's possible to click the form-level
button whenever you want — even while A) an AJAX request still is being processed, B) an AJAX request is yet to be fired.This is a long-standing weirdness/weakness in Drupal. Even on d.o — which runs Drupal 7 — you can get into trouble with this. Hundreds of issues/discussions/bug reports exist about this: https://www.google.com/search?q=drupal%20disable%20submit%20button%20whi...
In the case of the CKEditor 5 admin UI, the AJAX aspects are critical for it to work correctly (hence this bug report), and we're not yet doing what we can to ensure that all necessary AJAX requests + responses have ben processed.
I think this can be solved relatively simply for the CKEditor 5 admin UI (not generically):
When I applied those two measures, I also cannot reproduce the bugs anymore! 🥳 Curious what you think!
P.S.: to test this, I think we need something like:
Comment #31
Wim LeersIOW countless related ancient bugs:
😭
Comment #32
Wim LeersPer #30 … I'm pretty sure this is actually related to #3262484: AJAX desync when quickly changing multiple form values in plugin settings vertical tab, and doing what I proposed in #30 would fix that too? 🤓
Comment #33
nod_so #25 and #26 don't fix the no-js version of the form. I know it's a configuration for a editor that require JS but the fact that the form processing is broken in the no-js case, makes me think there is still a hidden issue somewhere. Would be needed for the solution below too.
I'm not too found of the hidden field solution. that feels like having a workaround for the fact that a disabled textarea isn't part of the submitted form, when we could easily remove the disabled attribute instead of having a copy of that field.
Disabling the save button when the change event gets triggered sounds like a good solution. I'm not sure what is achieved by disabling it on the input event (also, if we have a bunch of input events but no change event triggered (like you're typing something but then remove all changes no change event is triggered, how can we submit the form?)
The solution I like best though would be fixing the no-js form and removing the disabled attribute on the textarea if it exists when the form is submitted (even simpler than #15) with something like below would take care of this issue without having to disable the submit button.
Fixing the nojs form seems like it's a requirement given that there is an explicit fallback for the toobar config in a no-js situation.
Comment #34
Wim LeersYou'd mentioned this before but I'd forgotten about it. This is a really good point. I want to make this work.
Because as soon as any typing has happened in the "Manually editable HTML tags"
<textarea>
(or any other input), we need to wait for AJAX processing to ensure everything is in sync by the time the form is submitted. That's why it's listening to theinput
event, not thechange
event.You're right that this means that you cannot submit the form immediately after typing in that input, because that would not trigger the
change
event. And that's the very point.It'd be better if the
change
event were triggered automatically before the user could submit the form, but AFAIK that just doesn't happen. But perhaps you see a way?! 🤓The only alternative I see right now is to add an "Update" button next to the "Save configuration" button. It'd only be visible when there is not yet an in-progress AJAX request. And whenever it's visible, the "Save configuration" button would not be visible — so to the end user it'd be as if the "Save configuration" button sometimes changes to an "Update" button.
I … thought that would also trigger the
change
event? :OAgreed on your recommendation for a starting point. Now that you are providing reviews here, I'll start pairing with Ben on this one. Let me give it a try before he starts his day :)
Comment #35
nod_Change event is only triggered when there is an actual change to the form element value :)
Comment #36
Wim LeersAll morning went to #3231334: Global attributes (<* lang> and <* dir="ltr rtl">): validation + support (fix data loss) 😬
Comment #37
bnjmnmThis uses the approach suggested by @nod_ for the GHS field being empty, and retains the approach in #25 to update the allowed tags field correctly. I manually confirmed that part works even with JS disabled.
No interdiff as this is changed enough that it's easiest to classify this as a new approach.
Comment #38
nod_I know it's similar to what's below. We can do this too:
That could be written as
to make use of what once provides.
This doesn't work when we want to empty the "Manually editable HTML tags" field (since
$manually_editable_tags
is an empty array in that case). Not sure why we would want the plugin and not have anything configured, but it's unexpected. I try to add a tag, it's added properly to the allowed html, but if I try to remove it it doesn't update the allowed html field and the tag is still allowed.only 3 is NW, 1 and 2 are more details than anything.
Comment #39
nod_Source editing without extra editable html tags is actually valid, to see the generated html.
Comment #40
bnjmnmAddresses #38 - the Js suggestions are great and the removing-tags is addressed by checking is_array instead of true/false on the form input value.
Comment #41
nod_except for the CI checks failing this is RTBC for me.
Comment #42
nod_spoke a little too soon
this is probably the only question, shouldn't we use the submit event? there is a related issue with the ajax framework that right click trigger the call: #2616184: Right click should not submit buttons with Ajax behaviors
don't think we need jQuery here.
Comment #43
bnjmnmAddresses #42
Comment #44
nod_Thanks, looks good to me
Comment #45
alexpottI got stuck trying to work out what the 👇 and 👆 in the comment actually means. I think we should consider changing the form submission handler to be something like below. I think we can use variable names to make it easier to understand and I don't think optimising to not call
$form_state->getValue()
is necessary.Comment #46
brentgRan into this issue as well (adding tag to the source editing field, and when submitting seeing briefly an error message but still submitting)
The patch in #43 fixed this for me
Comment #47
bnjmnmAddress #45, good suggestion.
Comment #48
hooroomooConfirmed that #47 addresses feedback in #45
Comment #49
alexpottCredited @lauriii for filing the issue and @andregp for additional issue details and myself for patch review.
Committed 9117ebf and pushed to 10.0.x. Thanks!
Committed 2d19c2f and pushed to 9.4.x. Thanks!
Committed 925fab2 and pushed to 9.3.x. Thanks!
Backported to 9.3.x resolved some fun JS build conflicts on the way.