[2.x] fix: TextEditor.onbuild race against async Mithril redraw#4657
Merged
Conversation
`oncreate` called `_load().then(() => setTimeout(this.onbuild, 50))`.
The 50ms timer is a guess at how long it takes Mithril's async
`m.redraw()` to flush — under slow first-load conditions (cold CDN,
heavy DOM, additional `_loaders`) the redraw can land *after* the
timer fires, leaving `this.$('.TextEditor-editorContainer')[0]` as
`undefined` when `onbuild` runs. That `undefined` then propagates
into the editor driver (notably breaking Tiptap-based drivers like
fof/rich-text, where it produces a non-functional toolbar with no
editor underneath).
Use `m.redraw.sync()` in `_load` so the DOM is updated before the
promise resolves, and drop the 50ms timer entirely. The `.then` runs
on a microtask outside any Mithril event handler, so `redraw.sync()`
is safe here.
Closes #4612
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TextEditor.oncreateschedulesonbuildvia a hardcodedsetTimeout(..., 50)after_load()resolves._load()ends withm.redraw(), which is async (queued for the next frame). Under slow first-load conditions — cold CDN, heavy DOM, additional_loaderslike the one the emoji extension registers — the redraw can land after the 50ms timer fires, leaving the conditional<div class=\"TextEditor-editorContainer\">out of the DOM whenonbuildruns.When that happens,
this.\$('.TextEditor-editorContainer')[0]returnsundefinedand gets handed to whatever editor driver is being constructed:For the stock
BasicEditorDriver, this produces a broken textarea. For Tiptap-based drivers (e.g. fof/rich-text), Tiptap v3 only calls its internalmount()ifoptions.elementis truthy, so the view is permanently null and the composer renders a non-functional toolbar with no editor underneath. The bug is intermittent because it depends on whether the redraw beats the timer.Fix
Switch
_load's trailingm.redraw()tom.redraw.sync()and drop the 50ms timer inoncreate. Afterm.redraw.sync()returns, the DOM has been updated to reflectloading = false, so the container element is present whenonbuildruns. The.thencallback runs on a microtask outside any Mithril event handler, so callingm.redraw.sync()from there is safe.oncreate(vnode) { super.oncreate(vnode); - this._load().then(() => { - setTimeout(this.onbuild.bind(this), 50); - }); + this._load().then(this.onbuild.bind(this)); } _load() { return Promise.all(this._loaders.map((loader) => loader())).then(() => { this.loading = false; - m.redraw(); + m.redraw.sync(); }); }Test plan
Repro using the script from the linked issue:
this.\$('.TextEditor-editorContainer')[0]isundefined).Closes #4612