Engineering
established 10 years ago

This is a technical article covering how browsers muck up the underlying markup in an editable area as a user edits content. It turns out the problems center around opening up new lines using the ENTER key and merging them back together again using the BACKSPACE or DELETE keys.

If you dont go look you just wont know.

When you attempt to create an editor in a contenteditable div, the browser is in control and youre just along for the ride. Each browser has different ideas of what should be done to the underlying markup when a user presses ENTER to open up a new line or DELETE/BACKSPACE to merge lines back together. This conspires to make writing an editor that behaves as you would expect somewhat challenging.

In trying to develop this code, I realized that I didnt fully understand how the various browsers were interfering with the underlying markup as the user typed. I kept getting surprising results. So I decided to go look and carefully document what Ive found. Im putting this up here in the hopes that it might save someone else a bit of annoying work.

Exploring what happens on ENTER

I first explored all the cases related to the ENTER key that I could think of such as:

  • ENTER at the beginning of a line.
  • ENTER at the end.
  • ENTER in the middle.
  • ENTER in a line with pre-existing BR tags.
  • ENTER in a line with SPAN tags.
  • etc.

What I found was, in many cases, the browsers did not behave at all as I would have expected. To my surprise, MSIE (10) behaved more in line with my expectations than the other browsers. WebKit was the worst but FireFox likes to muck up the works with the best of them.

The markup is what was displayed using the developer tools. In MSIE, I used FireBug lite. In Chrome I used the Google Developer Tools and in FireFox I used FireBug proper.

Here are the results upon pressing ENTER in various circumstances:

as the first character in an empty editable div

WebKit:

<DIV><BR></DIV><DIV><BR></DIV>

FireFox:

<BR _moz_dirty=""><BR _moz_dirty="">

MSIE:

<P/><P/>

NOTE: Because of the <P>s MSIE lines appear double spaced.

after opening a new line, going back to the first and entering a character

WebKit:

<DIV>t</DIV><DIV><BR></DIV>

NOTE: THE BR DISAPPEARS

FireFox:

t<BR _moz_dirty=""><BR _moz_dirty="">

NOTE: pressing ENTER once inserts a single BR but does not open a new line.

MSIE:

<P>t</P><P/>

after opening a new line and entering a character on the second line

WebKit:

<DIV><BR></DIV> <DIV>t</DIV> -> BR DISAPPEARS

FireFox:

<BR _moz_dirty="">t<BR _moz_dirty="">

MSIE:

<P/><P>t</P>

end of first line with trailing BR
(initial content<div contenteditable=true>test<BR></DIV>)

WebKit:

test1<BR> -> UNEXPECTED<DIV><BR></DIV>

NOTE: INITIAL CONTENT SHOWS ONE LINE INSTEAD OF TWO.

FireFox:

test1<BR _moz_dirty=""><BR> -> UNEXPECTED that both BR's would show up.

NOTE: INITIAL CONTENT SHOWS ONE LINE INSTEAD OF TWO.

MSIE:

<P>test1</P><P><BR></P>

NOTE: INITIAL CONTENT SHOWS TWO LINES.

end of first line with embedded BR between lines
(initial content <DIV>test1<BR>test2</DIV> )

WebKit

test1<DIV><BR>test2</DIV>

FireFox

test1<BR _moz_dirty=""><BR>test2

MSIE

<P>test1</P><P><BR/>test2</P>

adding a character to initial content of <DIV><BR></DIV>

WebKit:

<DIV>t</DIV> -> UNEXPECTED. BR gets removed even though it was not added by WebKit

FireFox:

test1<BR _moz_dirty=""><BR>test2

MSIE:

<P>test1</P><P><BR/>test2</P>

after a line of text

WebKit:

test<DIV><BR></DIV>

FireFox:

test<BR _moz_dirty=""/><BR _moz_dirty="moz"/> -> DAFUQ??

MSIE:

<P>test</P><P/>

before a line of text

WebKit:

<DIV><BR></DIV>test

FireFox:

<BR _moz_dirty="">test

MSIE:

<P/><P>test</p>

in the middle of a line of text

WebKit:

te<DIV>st</DIV> -> UNEXPECTED

FireFox:

te<BR _moz_dirty="">st

MSIE:

<P>TE</P><P>ST</p>

before an image

WebKit:

<DIV><BR></DIV><IMG SRC="...">

FireFox:

<BR _moz_dirty="" /><IMG SRC="...">

MSIE:

<P/><P><IMG SRC="..."</P>

after an image

WebKit:

<img src="..."><DIV><BR></DIV>

FireFox:

<IMG SRC="..."><BR _moz_dirty="" /><BR _moz_dirty="" type="_moz" /> -> DAFUQ??

MSIE:

<P><IMG SRC="..."></P><P/>

between two images

WebKit:

<img src="..."><DIV><img src="..."></DIV> -> UNEXPECTED

FireFox:

<IMG SRC="..."><BR _moz_dirty="" /><IMG SRC="...">

MSIE:

<P><IMG SRC="..."></P><P><IMG SRC="..."></P>

before a span Using <SPAN>test</SPAN> as initial editable content.

WebKit:

<DIV><SPAN><BR></SPAN></DIV> -> UNEXPECTED

NOTE: can be explained by noting the cursor cannot be moved outside of the span in WebKit

FireFox:

<SPAN><BR _moz_dirty="" />test</SPAN> --> UNEXPECTED

MSIE:

<P><SPAN/></P> <P><SPAN>TEST</SPAN></P> --> UNEXPECTED

after a span

WebKit:

<SPAN>test</SPAN<DIV><SPAN><BR></SPAN></DIV> -> UNEXPECTED

NOTE: can be explained by noting the cursor cannot be moved outside of the span in WebKit.

FireFox:

<SPAN>test<BR _mod_dirty=""/><BR _moz_dirty="" type="_moz" /> --> DAFUQ?? UNEXPECTED</SPAN>

MSIE:

<P><SPAN>test</SPAN></P><P><SPAN/></P>

in the middle of a span

WebKit:

<SPAN>te</SPAN><DIV><SPAN>st</SPAN></DIV>

FireFox:

<SPAN>te<BR _moz_dirty=""/>st</SPAN>

MSIE:

<P><SPAN>te</SPAN></P><P><SPAN>st</SPAN></P>

Exploring what happens on DELETE/BACKSPACE

The other problematic case seems to be when lines are merged back together through the use of the DELETE or BACKSPACE keys. (The same situation likely arises in a multi-line selection thats typed over.) In the tests I move the cursor to the end of the first line and press DELETE to merge the lines together.

merging two lines of text separated by a <BR>
(initial content test1<BR>test2)

WebKit:

test1test2 (as two separate text nodes)

FireFox:

test1test2 (as a single text node)

MSIE

test1test2 (as a single text node)

two spans separated by a BR
(initial content <span>test1</span><BR><span>test2</span>)

WebKit

<span>test1</span><span>test2</span>   -> BR got deleted. inconsistent behavior

This seems like inconsistent behavior because typically you cannot get behind a <SPAN> in webkit.

FireFox

<span>test1</span><span>test2</span>

MSIE

<span>test1</span><span>test2</span>

text followed by a BR and SPAN
(initial content test1<BR><span>test2</span>)

WebKit

test1<span>test2</span>

FireFox

test1<span>test2</span>

MSIE

test1<span>test2</span>

text followed by a div
(initial content test1<div>test2</div>

WebKit

"test1""test2"    as two separate text nodes

FireFox

test1test2   as a single text node

MSIE

test1test2   as a single text node

two divs
(initial content <div>test1</div><div>test2</div>)

WebKit

<div>test1test2</div>    (as two text nodes

FireFox

<div>test1test2</div>

MSIE

<div>test1test2</div>

text followed by <BR> followed by DIV
(initial content test1<br><div>test2</div>)

WebKit

test1test2  as two text nodes --> UNEXPECTED, but makes sense after a bit of thought.

FireFox

test1test2

MSIE

test1test2

divs ending in BR
(initial content <div>test1<br></div><div>test2<br></div>

WebKit

<DIV>test1test2     as two text nodes --> UNEXPECTED. BOTH BRs gone.</DIV>

FireFox:

<DIV>test1test2<BR></DIV>

MSIE First DELETE has no visible effect on screen but consumes the BR:

<div>test1</div><div>test2<br></div>

There are obviously many other test cases that could be explored. Interestingly, as I was formatting this article I noticed that the WordPress WYSIWYG editor suffers from some of the same problems Ive identified above.

Merging lines turned out not to be as unexpected as I had feared however it did provide the interesting insight that MSIE and FireFox apparently merge multiple adjacent text nodes together while WebKit does not. This explains at least one bug Im seeing in my code.

    You must be a member of this group to post comments.

    Please see the top of the page to join.

    Link Details