EasyCorp / EasyCorp/EasyAdminBundle
Typing in a TextEditorField gets progressively slower on a form holding several Trix editors
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 4.3k
- Forks
- 1.1k
- Avg merge
- 8d 2h
- Merged PRs (30d)
- 11
Description
Typing in a TextEditorField gets progressively slower on a form holding several Trix editors
Symptom
On an edit form holding many Trix editors, typing lags, and the lag grows with every character typed. On one of our pages (31 editors, ~400 form fields), a single character insertion measured, five times in a row from a fresh page load:
142 ms → 228 ms → 294 ms → 349 ms → 398 ms
It never comes back down; only a page reload resets it.
Cause
assets/js/field-text-editor.js builds one TextEditorField per editor on the page:
// field-text-editor.js:11
document.addEventListener('trix-before-initialize', () => {
new TextEditorField();
});
trix-before-initialize fires once per <trix-editor> element, so a form with N editors ends up with N instances. Each instance adds two more document-level trix-change listeners:
#processRequiredAttribute()(line 67) →#markInvalidFormFields(), which re-scans.ea-edit-formwithquerySelectorAll('input,select,textarea')and callssetCustomValidity()on every field.#enableFormChangesDetection()(line 75) →new DirtyForm(form).
DirtyForm's constructor walks the whole form.elements collection and attaches a new change + input listener to each field. Those listeners are never removed, and the bound callbacks are new function objects each time, so nothing is deduplicated.
So one keystroke costs N_editors × N_fields, and it also leaves N_editors × N_fields × 2 extra listeners behind, which the next keystroke pays for again. Hence the linear growth above.
Repro
- Any CRUD edit page with, say, 20+
TextEditorFieldinstances (ours are nested inside aCollectionField). - Open the console and run this a few times in a row:
const e = document.querySelector('trix-editor');
const t = performance.now(); e.editor.insertString('a'); console.log(performance.now() - t);
The number rises on every call.
Suggested fix
Two independent points:
- Build
TextEditorFieldonce.#processRequiredAttribute()and#handleFormSubmission()already query the whole document / the submitted form, so a single instance covers every editor. A guard on the listener would do:
let textEditorField = null;
document.addEventListener('trix-before-initialize', () => {
textEditorField ??= new TextEditorField();
});
(#processRequiredAttribute() would then need to run again for editors added later — an ea.collection.item-added listener, or re-running just the per-element part on trix-before-initialize.)
- Drop
#enableFormChangesDetection()entirely.assets/js/form.jsalready instantiatesDirtyFormonce onDOMContentLoadedfor.ea-new-form/.ea-edit-form, andDirtyFormhandles<trix-editor>natively (it collectsform.querySelectorAll('trix-editor')and bindstrix-changeon them). The rebuild here looks redundant — and because it re-snapshotsinitialValueson every keystroke, it arguably weakens the very detection it is meant to provide.
Point 2 alone removes the unbounded growth; point 1 removes the × N_editors factor.
Workaround
For anyone hitting this before a fix lands: keeping trix-change inside the editor it came from (stopPropagation() on the <trix-editor> element, which still lets form.js's own DirtyForm see it — it listens on that element, not on the document) brought that same measurement from 142 ms down to ~15 ms. It does mean taking over #markInvalidFormFields() for those fields, which is two lines given data-ea-trix-is-required is already on the textarea.
Versions
- easycorp/easyadmin-bundle 5.5.0
- Symfony 8.x, PHP 8.4
- Chrome
Investigation and write-up done with Claude Code, on a real project of mine. The timings above are actual measurements I ran in my own browser console, not estimates.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with assets/js/field-text-editor.js, especially the trix-before-initialize handler, #processRequiredAttribute(), and #enableFormChangesDetection(); then compare its DirtyForm usage with assets/js/form.js. Reproduce the issue on a form with several Trix editors and use the console timing snippet. Done means typing no longer gets progressively slower and redundant document-level listeners are avoided.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, php, symfony
- Domain
- frontend, performance
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100