contenteditable + ::first-letter + Chrome = buggy combo

Published on – Tags:

This combination causes weird behavior and data loss because of a bug in Chrome.

Table of contents

Context: buggy TinyMCE editor

Note: here's how I stumbled upon the bug, but the bug is not specific to TinyMCE.

At work, I was investigating buggy and weird behavior of dropcap paragraphs (implemented using the ::first-letter CSS pseudo-element) in a customized TinyMCE editor (which uses the contenteditable attribute).

We had customized our TinyMCE editor to show a stylized initial letter – a dropcap – in the first paragraph. The idea was to make the text look the same in the editor as it looks on the site. The customization was implemented using CSS like this, though in a wee bit more sophisticated way:

#tinymce > p:first-child::first-letter {
font-size: 2em;
font-weight: bold;
}

Then a bug report came in. It stated that there were two problems in the TinyMCE editor when editing a dropcap paragraph (i.e. the first paragraph): the caret position was incorrect, and sometimes there was data loss. These problems occurred when editing dropcap paragraphs in Chrome, but not in other browsers.

Problems with the combination

After quick testing, I confirmed that I could reproduce the problems, with two different kinds of data loss:

  • Incorrect caret position. When the caret was in the dropcap paragraph, its position was one character too much to the right.
  • Minor data loss. When the caret was at the beginning of the second paragraph, pressing Backspace also removed the last character of the first paragraph. The same happened by pressing Delete when the caret was at the end of the first paragraph. Normally these operations would just remove the linebreak between the paragraphs, so that the contents of the second paragraph are moved to the end of the first paragraph.
  • Major data loss. When the caret was at the end of the dropcap paragraph, pressing Backspace emptied the whole paragraph! Looking at Chrome DevTools, the <p> tag was still there, but it was empty. The same happened by pressing Delete when the caret was at the second last position of the first paragraph. Normally these operations would just remove the last character of the paragraph.

Yikes! When investigating the issue, I found that it's not related to TinyMCE per se, but present in any contenteditable element that has a ::first-letter pseudo-element.

The root cause: a bug in Chrome

Long story short: this is a bug in Chrome. The "Contenteditable with :first-letter styling" Chromium issue was reported in 2013 (currently 7.5 years ago) and hasn't been fixed yet.

The fix: don't use the combination

The issue was not in our implementation, nor was it in TinyMCE. It was in Chrome itself, so there was nothing that we could reasonably do.

I suggested that we get rid of the combination, i.e. get rid of the dropcap styling in the TinyMCE editor, and that's what we did.

It wasn't that big of a loss in the end; we anyway had an accurate preview mode. We even had an "on-page editing" mode as well. It looks like the preview mode, but the editor can edit the contents in place and see the changes immediately reflected on the page.

How I investigated the issue

Note: this was a simple 30-minute investigation, so nothing special. But I hope that this short story of how I approached the issue will be helpful to some by showing one way of tackling a bug report like this.

After managing to reproduce the reported problems, I investigated and solved the issue in the following steps:

  1. I first tested what styles of the ::first-letter pseudo-element cause the problems by tweaking the styles in Chrome DevTools. After quick fiddling, it was apparent that any single valid style causes the problems. Because of this, it looked unlikely that the issue was in our CSS.

  2. Thinking that this was an issue in TinyMCE, I googled for queries like "tinymce first-letter" to look for bug reports and solutions. But all I could find was one TinyMCE issue on GitHub from 2016 which was closed in 2018 without a solution. "It seems to be a problem with CSS and not TinyMCE itself" they said.

  3. Hmm, what could I try next... We were using TinyMCE version 4 and the latest was version 5. So maybe the issue had been fixed ages ago (in TinyMCE version 5) and everyone had already upgraded to version 5, so that's why no one was talking about this? Note that I was still thinking that the issue was in TinyMCE.

    Well, let's try. I remembered that there's a live demo of TinyMCE on its homepage. I added dropcap stylings to the demo using Chrome DevTools, and... Bleh, version 5 had the same problems.

  4. But wait! While tweaking the styles, I noticed that the TinyMCE demo used the contenteditable attribute. Could it be that this was not a styling issue (despite the GitHub issue suggesting otherwise) but related to the contenteditable attribute?

    I created a quick 2-minute fiddle of the contenteditable + ::first-letter combo at flems.io – and voilà! My quick fiddle had the same problems.

  5. Because the contenteditable attribute is more common than TinyMCE, I now had higher hopes of finding relevant results. When googling for "contenteditable first-letter," the first SO question that I opened had this snippet of wisdom in the top-voted answer by Marc J (thanks Marc!):

    I just noticed that there is a bug in Chrome that causes the cursor to become in the wrong spot with contenteditable and ::first-letter.

And that was it! Some fiddling, googling, and a little bit of luck.