Phenomenon
The contenteditable="plaintext-only" attribute was designed to eliminate the need for manual paste event interception. However, in early 2024 (Chromium 121), it was discovered that the engine incorrectly converts or preserves leading/trailing spaces as (U+00A0) instead of standard spaces (U+0020). Since prevents automatic line wrapping in CSS, pasted text often “breaks” the UI layout by overflowing its container.
Reproduction Steps
- Create a
<div>withcontenteditable="plaintext-only"and a fixed width (e.g.,200px). - Copy a long sentence from a source that contains multiple spaces or is formatted.
- Paste the text into the div using the browser’s default behavior.
- Inspect the resulting DOM string or observe the wrapping behavior.
Observed Behavior
- DOM Structure: The pasted text is indeed plain text (no tags), but character examination reveals
entities. - Layout: The text remains on a single line, causing horizontal overflow or breaking the parent layout.
- Selection: Triple-clicking to select the line often includes the invisible
characters, making further processing (like URL detection) fail.
Expected Behavior
All whitespace character sequences should be normalized to a single or multiple standard breaking spaces (U+0020), respecting the CSS white-space property of the container.
Impact
- Broken UI Layout: Sidebars, chat bubbles, and comments overflow unexpectedly.
- Search Failures: Internal search logic for the application may not match
when searching for standard spaces. - Copy-Paste Loops: If the user copies the text again and pastes it elsewhere, the
“infection” spreads to other applications.
Browser Comparison
- Chrome / Edge (v121-122): Confirmed regression in
plaintext-onlymode. - Safari: Handles
plaintext-onlyconversion by normalizing to standard spaces. - Firefox: Correct normalization during plain text extraction.
References & Solutions
Mitigation: Manual Normalization
Even with plaintext-only, you may need a short-lived listener to clean up the browser’s output if you support affected Chromium versions.
element.addEventListener('input', (e) => {
if (element.innerHTML.includes(' ')) {
// Warning: This can disrupt caret position if not handled carefully
element.innerHTML = element.innerHTML.replace(/ /g, ' ');
}
});