Problem
src/main/webapp/WEB-INF/jsp/cms/configureContent.jsp (the cms portlet variant of SimpleContentPortlet) has no plain Save button on the form. Saving relies entirely on the Save plugin in the CKEditor 4 toolbar (<a class=\"cke_button__save\">), which submits the underlying form via an internal callback chain. The sibling webcomponent variant has an explicit <button type=\"submit\"> and saves cleanly.
Two consequences:
- Brittle for end-users. If anything strips or disables the CKE Save plugin (custom build, future major-version upgrade, plugin reorder, etc.), the form has no usable submit affordance. The visible "Return without saving" button is on a separate form whose action is
?pP_action=cancel.
- Brittle for automated tests. Any E2E flow that programmatically populates the editor and submits hits a CKEditor 4 race:
editor.setData(html, callback) resolves before CKE finishes wiring its internal model back to the textarea, and the editor's submit-time hook re-syncs from a stale model. The form posts the previous content roughly half the time. This isn't theoretical — we hit this writing Playwright coverage for SimpleContent against uPortal-start (tests/ux/portlets/simple-content.spec.ts). The race never fired in isolated runs but reproduced ~50% in the full suite.
What we're shipping in tests as a workaround
const ck = window.CKEDITOR;
const id = Object.keys(ck.instances)[0];
ck.instances[id].destroy(true); // \`true\` = updateElement on teardown
const textarea = document.querySelector(\"textarea[id$='content']\");
textarea.value = newContent;
textarea.form.submit();
Tearing the editor down before touching the textarea removes every CKE surface that could re-sync, so the form posts exactly what we set. We didn't find a setData-only sequence (event-listening on dataReady, double updateElement(), micro-task yields) that was reliable across full-suite runs.
Proposed product fix
Mirror the webcomponent variant in the cms variant: add an explicit submit button to cms/configureContent.jsp:
<form:form id=\"${n}contentForm\" modelAttribute=\"form\" action=\"${formUrl}\" method=\"post\">
<form:textarea id=\"${n}content\" path=\"content\"/>
<div class=\"btn-group\">
<button type=\"submit\" name=\"update\" value=\"update\" class=\"btn-primary\">
<spring:message code=\"configurationForm.save\"/>
</button>
</div>
</form:form>
Benefits:
- Saving no longer depends on CKEditor's Save plugin being present and wired.
- The toolbar Save can stay (it still works); this is an additive belt-and-suspenders.
- Automated tests get a stable target. Click the visible button, done — no
destroy(true) dance.
Repro for the test race (background, not required for the product fix)
- Open any
cms-variant portlet in config mode (e.g. /p/what-is-uportal/max/render.uP?pCm=config).
- From DevTools / Playwright, run
editor.setData('<p>NEW</p>', () => { editor.updateElement(); document.querySelector('form[id$=\"contentForm\"]').submit(); }).
- Inspect the POST body in the network tab. About half the time,
content= is the previous HTML instead of <p>NEW</p>.
- Replace the inner sequence with
editor.destroy(true); textarea.value = '<p>NEW</p>'; textarea.form.submit(); — the POST body is correct every time.
Related
Problem
src/main/webapp/WEB-INF/jsp/cms/configureContent.jsp(thecmsportlet variant of SimpleContentPortlet) has no plain Save button on the form. Saving relies entirely on the Save plugin in the CKEditor 4 toolbar (<a class=\"cke_button__save\">), which submits the underlying form via an internal callback chain. The siblingwebcomponentvariant has an explicit<button type=\"submit\">and saves cleanly.Two consequences:
?pP_action=cancel.editor.setData(html, callback)resolves before CKE finishes wiring its internal model back to the textarea, and the editor's submit-time hook re-syncs from a stale model. The form posts the previous content roughly half the time. This isn't theoretical — we hit this writing Playwright coverage for SimpleContent against uPortal-start (tests/ux/portlets/simple-content.spec.ts). The race never fired in isolated runs but reproduced ~50% in the full suite.What we're shipping in tests as a workaround
Tearing the editor down before touching the textarea removes every CKE surface that could re-sync, so the form posts exactly what we set. We didn't find a setData-only sequence (event-listening on
dataReady, doubleupdateElement(), micro-task yields) that was reliable across full-suite runs.Proposed product fix
Mirror the
webcomponentvariant in thecmsvariant: add an explicit submit button tocms/configureContent.jsp:Benefits:
destroy(true)dance.Repro for the test race (background, not required for the product fix)
cms-variant portlet in config mode (e.g./p/what-is-uportal/max/render.uP?pCm=config).editor.setData('<p>NEW</p>', () => { editor.updateElement(); document.querySelector('form[id$=\"contentForm\"]').submit(); }).content=is the previous HTML instead of<p>NEW</p>.editor.destroy(true); textarea.value = '<p>NEW</p>'; textarea.form.submit();— the POST body is correct every time.Related