WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
NEW
327674
Stale caret stays painted after Selection.collapse() when the contenteditable then moves up
https://bugs.webkit.org/show_bug.cgi?id=327674
Summary
Stale caret stays painted after Selection.collapse() when the contenteditable...
Leo Wandersleb
Reported
2026-10-15 01:50:47 PDT
Steps: open the attached test case, click the box, type a word, press Enter twice quickly. Expected: one caret, on line 3. Actual: a second, steady caret stays on line 2 until the next keystroke. The box is pinned to the bottom, so each new line moves it up. The Enter handler inserts a `<br>` and places the caret with `Selection.collapse()`, the way ProseMirror does. WebKit keeps the caret position it computed before the move. Seen 3/3 in WebKitGTK 2.52.6. It doesn't happen when the box is top-anchored (`?top`, 0/3) or with `removeAllRanges()` + `addRange()` (0/4). Transform, will-change, contain, isolation, column-reverse and absolute bottom positioning still ghost. This hits every ProseMirror/Tiptap composer that grows upward. Related: * [ProseMirror's workaround for selection changes](
https://github.com/ProseMirror/prosemirror/issues/1092
) * [ours for the layout case](
https://github.com/block/buzz/pull/8355
) * possibly
bug 140874
. Test case (`ghost-caret.html`): ```html <!doctype html> <meta charset="utf-8"> <title>Stale caret after a bottom-anchored contenteditable grows</title> <style> body { margin: 0; font: 15px/20px sans-serif; } #outer { height: 100vh; display: flex; flex-direction: column; justify-content: flex-end; padding: 20px; box-sizing: border-box; } #ed { border: 1px solid #ccc; padding: 8px; width: 380px; outline: none; } #ed p { margin: 0; } </style> <p style="position:absolute;top:0;left:20px">Click the box, type a word, then press Enter twice quickly. A second, steady caret stays on the middle line. <a href="?top">Top-anchored control</a></p> <div id="outer"><div id="ed" contenteditable><p><br></p></div></div> <script> if (location.search === "?top") document.getElementById("outer").style.justifyContent = "flex-start"; const ed = document.getElementById("ed"); // Insert a line break and place the caret after it with Selection.collapse(), // as ProseMirror does. Using removeAllRanges() + addRange() instead does not ghost. ed.addEventListener("keydown", (e) => { if (e.key !== "Enter") return; e.preventDefault(); const p = ed.querySelector("p"); let trailing = p.querySelector("br.trailing"); if (!trailing) { trailing = document.createElement("br"); trailing.className = "trailing"; p.append(trailing); } p.insertBefore(document.createElement("br"), trailing); getSelection().collapse(p, p.childNodes.length - 1); }); </script> ```
Attachments
the example
(1.41 KB, text/html)
2026-10-15 02:03 PDT
,
Leo Wandersleb
no flags
Details
View All
Add attachment
proposed patch, testcase, etc.
Leo Wandersleb
Comment 1
2026-10-15 02:03:25 PDT
Created
attachment 481835
[details]
the example The issue text has the html code embedded but for your convenience here it is as an html file.
Radar WebKit Bug Importer
Comment 2
2026-10-22 01:51:12 PDT
<
rdar://problem/190703718
>
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug