-
-
Notifications
You must be signed in to change notification settings - Fork 362
Description
We are using TipTap for our editors, but we have debugged this issue down to ProseMirror.
When initialising the editor (where Prosemirror is attaching to an fresh DOM element), in patchAttributes
(reference), the dom.setAttribute
statement would set contenteditable
to false
(where it was undefined and false by default).
Per this gist and its accompanying web.dev post, we have found that this setting of contenteditable
does trigger an immediate style recalculation through profiling on the Prosemirror example. This happens even if one sets from undefined
(new DOM element -- default to false
) to false
.

This itself is not an issue with a simple page like this and with one editor; however our use case requires us to use multiple nested editors on a reasonably complex article (e.g. embedded, interactive elements that also uses editors to render its rich text content). This causes multiple setAttributes
(across the main and embedded editors) to be called in the same tick, so the browser hangs (and all style recaluations except for the last one in the tick are marked as "forced reflow"). In some extreme cases (where over 50 editors are displayed at the same tick) this causes Interaction-to-Pointer (ITP) delays of over 10 seconds (as the page is very complex with other elements other than the editor). Without these forced reflows, the page performance could increase by more than 90%.
In the image above, the next "Recalculate style", triggered by a nested editor within the same browser tick (as multiple editors are being initialised together) makes the previous style recalculation redundant.
To confirm the issue further, we added debug statements:
function patchAttributes(dom, prev, cur) {
console.log("PATCH CUR IND", cur);
for (let name in prev)
if (name != "class" && name != "style" && name != "nodeName" && !(name in cur))
dom.removeAttribute(name);
for (let name in cur)
if (name != "class" && name != "style" && name != "nodeName" && cur[name] != prev[name]) {
dom.setAttribute(name, cur[name]);
if (name === "contenteditable") {
console.log("SET ATTR", name, cur[name], dom, prev, JSON.stringify(cur)); // <-- debug
console.trace();
}
}
and we observed that prosemirror was trying to set contenteditable
to false
.
Guarding the dom.setAttribute
using:
if (dom.getAttribute(name) !== cur[name])
Seems to have resolved the issue - by default, the DOM element has contenteditable=false
so in this case it will skip the setting of contenteditable
while initialising, thus avoiding the style recalc/repaint.
Could this please be confirmed by Prosemirror's end?