Spec and implement insertLineBreak
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Sun, 19 Jun 2011 14:39:00 -0600
changeset 297 b8bed880fd3b
parent 296 6a6423b5f64d
child 298 7ce14572f0b5
Spec and implement insertLineBreak
editcommands.html
implementation.js
insertlinebreaktest.html
source.html
tests.js
--- a/editcommands.html	Sun Jun 19 13:53:21 2011 -0600
+++ b/editcommands.html	Sun Jun 19 14:39:00 2011 -0600
@@ -115,15 +115,16 @@
    <li><a href=#the-inserthtml-command><span class=secno>7.13 </span>The <code title="">insertHTML</code> command</a></li>
    <li><a href=#the-insertimage-command><span class=secno>7.14 </span>The <code title="">insertImage</code> command</a></li>
    <li><a href=#the-inserthorizontalrule-command><span class=secno>7.15 </span>The <code title="">insertHorizontalRule</code> command</a></li>
-   <li><a href=#the-insertorderedlist-command><span class=secno>7.16 </span>The <code title="">insertOrderedList</code> command</a></li>
-   <li><a href=#the-insertparagraph-command><span class=secno>7.17 </span>The <code title="">insertParagraph</code> command</a></li>
-   <li><a href=#the-inserttext-command><span class=secno>7.18 </span>The <code title="">insertText</code> command</a></li>
-   <li><a href=#the-insertunorderedlist-command><span class=secno>7.19 </span>The <code title="">insertUnorderedList</code> command</a></li>
-   <li><a href=#the-justifycenter-command><span class=secno>7.20 </span>The <code title="">justifyCenter</code> command</a></li>
-   <li><a href=#the-justifyfull-command><span class=secno>7.21 </span>The <code title="">justifyFull</code> command</a></li>
-   <li><a href=#the-justifyleft-command><span class=secno>7.22 </span>The <code title="">justifyLeft</code> command</a></li>
-   <li><a href=#the-justifyright-command><span class=secno>7.23 </span>The <code title="">justifyRight</code> command</a></li>
-   <li><a href=#the-outdent-command><span class=secno>7.24 </span>The <code title="">outdent</code> command</a></ol></li>
+   <li><a href=#the-insertlinebreak-command><span class=secno>7.16 </span>The <code title="">insertLineBreak</code> command</a></li>
+   <li><a href=#the-insertorderedlist-command><span class=secno>7.17 </span>The <code title="">insertOrderedList</code> command</a></li>
+   <li><a href=#the-insertparagraph-command><span class=secno>7.18 </span>The <code title="">insertParagraph</code> command</a></li>
+   <li><a href=#the-inserttext-command><span class=secno>7.19 </span>The <code title="">insertText</code> command</a></li>
+   <li><a href=#the-insertunorderedlist-command><span class=secno>7.20 </span>The <code title="">insertUnorderedList</code> command</a></li>
+   <li><a href=#the-justifycenter-command><span class=secno>7.21 </span>The <code title="">justifyCenter</code> command</a></li>
+   <li><a href=#the-justifyfull-command><span class=secno>7.22 </span>The <code title="">justifyFull</code> command</a></li>
+   <li><a href=#the-justifyleft-command><span class=secno>7.23 </span>The <code title="">justifyLeft</code> command</a></li>
+   <li><a href=#the-justifyright-command><span class=secno>7.24 </span>The <code title="">justifyRight</code> command</a></li>
+   <li><a href=#the-outdent-command><span class=secno>7.25 </span>The <code title="">outdent</code> command</a></ol></li>
  <li><a href=#miscellaneous-commands><span class=secno>8 </span>Miscellaneous commands</a>
   <ol>
    <li><a href=#the-selectall-command><span class=secno>8.1 </span>The <code title="">selectAll</code> command</a></li>
@@ -148,8 +149,8 @@
   multipleSelection, overwrite, print, refresh, saveAs, unbookmark: Mentioned
   in MSDN docs but not MDC, so presumably IE-only.  Some of these seem
   inappropriate or useless, others will bear investigation.
-* findString, fontSizeDelta, insertLineBreak, insertNewlineInQuotedContent,
-  justifyNone, print, transpose: There's code for these in WebKit,
+* findString, fontSizeDelta, insertNewlineInQuotedContent, justifyNone, print,
+  transpose: There's code for these in WebKit,
   Source/WebCore/editing/EditorCommand.cpp, but I didn't see them mentioned
   elsewhere.  Some might be worth adding.
 * unselect: Seems to not be implemented by Gecko or Opera, and IE behaves
@@ -5192,13 +5193,73 @@
 </ol>
 
 
-<h3 id=the-insertorderedlist-command><span class=secno>7.16 </span><dfn>The <code title="">insertOrderedList</code> command</dfn></h3>
+<h3 id=the-insertlinebreak-command><span class=secno>7.16 </span><dfn>The <code title="">insertLineBreak</code> command</dfn></h3>
+
+<p><a href=#action>Action</a>:
+
+<!--
+Only implemented in WebKit (Chrome 14 dev).  Other tests are entirely manual.
+There's a surprisingly large amount of interop.
+
+IE9 is tripped up by <xmp>, and also often doesn't add an extra <br> when the
+one it just inserted is extraneous.
+
+Firefox 6.0a2 doesn't notice if you're trying to put the <br> in a bad place,
+which can result in unserializable DOMs.
+
+Chrome 14 dev inserts a literal linebreak for pre and xmp and maybe other
+similar elements.  This doesn't seem very useful, so I don't bother.
+
+Opera 11.11 isn't heedful of <xmp>, and treats <pre> somewhat oddly.
+-->
+
+<ol>
+  <li><a href=#delete-the-contents>Delete the contents</a> of the <a href=#active-range>active range</a>.
+
+  <li>If the <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>, and
+  "br" is not an <a href=#allowed-child>allowed child</a> of it, abort these steps.
+  <!-- script, xmp, table, . . . -->
+
+  <li>If the <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is not an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>,
+  and "br" is not an <a href=#allowed-child>allowed child</a> of the <a href=#active-range>active
+  range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>, abort these steps.
+
+  <!-- We don't want to call insertNode at the start or end of a text node,
+  because that will leave an empty text node. -->
+  <li>If the <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node and
+  its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is zero, set the <a href=#active-range>active range</a>'s
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> to (<a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>, <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>).
+
+  <li>If the <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node and
+  its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>, set the
+  <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> to (<a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>, 1 + <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>).
+
+  <li>Let <var title="">br</var> be the result of calling <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on the
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>.
+
+  <li>Call <code class=external data-anolis-spec=domrange title=dom-Range-insertNode><a href=http://html5.org/specs/dom-range.html#dom-range-insertnode>insertNode(<var title="">br</var>)</a></code> on the <a href=#active-range>active range</a>.
+
+  <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse()</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>, with
+  <var title="">br</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> as the first argument and one plus <var title="">br</var>'s
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> as the second argument.
+
+  <li>If <var title="">br</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-nextSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling>nextSibling</a></code> is null and <var title="">br</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>
+  is not an <a href=#inline-node>inline node</a>, or if <var title="">br</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-nextSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling>nextSibling</a></code> is
+  not null and not an <a href=#inline-node>inline node</a>, call <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on
+  the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a> and let <var title="">extra br</var> be the result, then call
+  <code class=external data-anolis-spec=domrange title=dom-Range-insertNode><a href=http://html5.org/specs/dom-range.html#dom-range-insertnode>insertNode(<var title="">extra br</var>)</a></code> on the <a href=#active-range>active range</a>.
+</ol>
+
+
+<h3 id=the-insertorderedlist-command><span class=secno>7.17 </span><dfn>The <code title="">insertOrderedList</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
 "ol".
 
 
-<h3 id=the-insertparagraph-command><span class=secno>7.17 </span><dfn>The <code title="">insertParagraph</code> command</dfn></h3>
+<h3 id=the-insertparagraph-command><span class=secno>7.18 </span><dfn>The <code title="">insertParagraph</code> command</dfn></h3>
 
 <!--
 There are three major behaviors here.  Firefox 5.0a2 behaves identically to
@@ -5449,7 +5510,7 @@
 </ol>
 
 
-<h3 id=the-inserttext-command><span class=secno>7.18 </span><dfn>The <code title="">insertText</code> command</dfn></h3>
+<h3 id=the-inserttext-command><span class=secno>7.19 </span><dfn>The <code title="">insertText</code> command</dfn></h3>
 
 <!--
 Supported only by WebKit.  Tests in other browsers were manual.  In the manual
@@ -5538,37 +5599,37 @@
 </ol>
 
 
-<h3 id=the-insertunorderedlist-command><span class=secno>7.19 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
+<h3 id=the-insertunorderedlist-command><span class=secno>7.20 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
 "ul".
 
 
-<h3 id=the-justifycenter-command><span class=secno>7.20 </span><dfn>The <code title="">justifyCenter</code> command</dfn></h3>
+<h3 id=the-justifycenter-command><span class=secno>7.21 </span><dfn>The <code title="">justifyCenter</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "center".
 
 
-<h3 id=the-justifyfull-command><span class=secno>7.21 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
+<h3 id=the-justifyfull-command><span class=secno>7.22 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "justify".
 
 
-<h3 id=the-justifyleft-command><span class=secno>7.22 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
+<h3 id=the-justifyleft-command><span class=secno>7.23 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "left".
 
 
-<h3 id=the-justifyright-command><span class=secno>7.23 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
+<h3 id=the-justifyright-command><span class=secno>7.24 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "right".
 
 
-<h3 id=the-outdent-command><span class=secno>7.24 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
+<h3 id=the-outdent-command><span class=secno>7.25 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
--- a/implementation.js	Sun Jun 19 13:53:21 2011 -0600
+++ b/implementation.js	Sun Jun 19 14:39:00 2011 -0600
@@ -5322,11 +5322,82 @@
 };
 //@}
 
+///// The insertLineBreak command /////
+//@{
+commands.insertlinebreak = {
+	action: function(value) {
+		// "Delete the contents of the active range."
+		deleteContents(getActiveRange());
+
+		// "If the active range's start node is an Element, and "br" is not an
+		// allowed child of it, abort these steps."
+		if (getActiveRange().startContainer.nodeType == Node.ELEMENT_NODE
+		&& !isAllowedChild("br", getActiveRange().startContainer)) {
+			return;
+		}
+
+		// "If the active range's start node is not an Element, and "br" is not
+		// an allowed child of the active range's start node's parent, abort
+		// these steps."
+		if (getActiveRange().startContainer.nodeType != Node.ELEMENT_NODE
+		&& !isAllowedChild("br", getActiveRange().startContainer.parentNode)) {
+			return;
+		}
+
+		// "If the active range's start node is a Text node and its start
+		// offset is zero, set the active range's start and end to (parent of
+		// start node, index of start node)."
+		if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE
+		&& getActiveRange().startOffset == 0) {
+			getActiveRange().setStart(getActiveRange().startContainer.parentNode, getNodeIndex(getActiveRange().startContainer));
+			getActiveRange().setEnd(getActiveRange().startContainer.parentNode, getNodeIndex(getActiveRange().startContainer));
+		}
+
+		// "If the active range's start node is a Text node and its start
+		// offset is the length of its start node, set the active range's start
+		// and end to (parent of start node, 1 + index of start node)."
+		if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE
+		&& getActiveRange().startOffset == getNodeLength(getActiveRange().startContainer)) {
+			getActiveRange().setStart(getActiveRange().startContainer.parentNode, 1 + getNodeIndex(getActiveRange().startContainer));
+			getActiveRange().setEnd(getActiveRange().startContainer.parentNode, 1 + getNodeIndex(getActiveRange().startContainer));
+		}
+
+		// "Let br be the result of calling createElement("br") on the context
+		// object."
+		var br = document.createElement("br");
+
+		// "Call insertNode(br) on the active range."
+		getActiveRange().insertNode(br);
+
+		// "Call collapse() on the context object's Selection, with br's parent
+		// as the first argument and one plus br's index as the second
+		// argument."
+		getActiveRange().setStart(br.parentNode, 1 + getNodeIndex(br));
+		getActiveRange().setEnd(br.parentNode, 1 + getNodeIndex(br));
+
+		// "If br's nextSibling is null and br's parent is not an inline node,
+		// or if br's nextSibling is not null and not an inline node, call
+		// createElement("br") on the context object and let extra br be the
+		// result, then call insertNode(extra br) on the active range."
+		if ((!br.nextSibling && !isInlineNode(br.parentNode))
+		|| (br.nextSibling && !isInlineNode(br.nextSibling))) {
+			getActiveRange().insertNode(document.createElement("br"));
+
+			// Compensate for nonstandard implementations of insertNode
+			getActiveRange().setStart(br.parentNode, 1 + getNodeIndex(br));
+			getActiveRange().setEnd(br.parentNode, 1 + getNodeIndex(br));
+		}
+	}
+};
+//@}
+
 ///// The insertOrderedList command /////
+//@{
 commands.insertorderedlist = {
 	// "Toggle lists with tag name "ol"."
 	action: function() { toggleLists("ol") }
 };
+//@}
 
 ///// The insertParagraph command /////
 //@{
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/insertlinebreaktest.html	Sun Jun 19 14:39:00 2011 -0600
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Manual insertLineBreak (Shift-Enter) tests</title>
+<link rel=stylesheet href=tests.css>
+<p>Legend: {[ are the selection anchor, }] are the selection focus, {}
+represent an element boundary point, [] represent a text node boundary point.
+Syntax and some of the tests taken from <a
+href=http://www.browserscope.org/richtext2/test>Browserscope</a>.  data-start
+and data-end attributes also represent element boundary points, with the node
+being the element with the attribute and the offset given as the attribute
+value, for cases where HTML parsing doesn't allow text nodes.  Currently we
+don't really pay attention to reversed selections at all, so they might get
+displayed as forwards or such.
+
+<p><input type=button value="Clear cached results" onclick="clearCachedResults()">
+
+<div id=tests>
+	<input type=button value="Run tests" onclick="runTests()">
+	<table border=1><tr><th>Input<th>Spec<th>Browser<th>Same</table>
+	<p><label>New test input: <input></label> <input type=button value="Add test" onclick="addTest()">
+</div>
+
+<div id=overlay>Tap Shift-Enter (or Mac equivalent) repeatedly until this
+annoying message disappears!  (But not too quickly.  And don't hit any other
+keys or click with the mouse anywhere, it will mess it up.<span id=testcount>
+<span></span> manual test(s) remain.</span>)</div>
+
+<script src=implementation.js></script>
+<script src=tests.js></script>
+<script>
+var command = "insertlinebreak";
+var keyname = "insertlinebreak";
+</script>
+<script src=manualtest.js></script>
--- a/source.html	Sun Jun 19 13:53:21 2011 -0600
+++ b/source.html	Sun Jun 19 14:39:00 2011 -0600
@@ -83,8 +83,8 @@
   multipleSelection, overwrite, print, refresh, saveAs, unbookmark: Mentioned
   in MSDN docs but not MDC, so presumably IE-only.  Some of these seem
   inappropriate or useless, others will bear investigation.
-* findString, fontSizeDelta, insertLineBreak, insertNewlineInQuotedContent,
-  justifyNone, print, transpose: There's code for these in WebKit,
+* findString, fontSizeDelta, insertNewlineInQuotedContent, justifyNone, print,
+  transpose: There's code for these in WebKit,
   Source/WebCore/editing/EditorCommand.cpp, but I didn't see them mentioned
   elsewhere.  Some might be worth adding.
 * unselect: Seems to not be implemented by Gecko or Opera, and IE behaves
@@ -5205,6 +5205,66 @@
 </ol>
 <!-- @} -->
 
+<h3><dfn>The <code title>insertLineBreak</code> command</dfn></h3>
+<!-- @{ -->
+<p><span>Action</span>:
+
+<!--
+Only implemented in WebKit (Chrome 14 dev).  Other tests are entirely manual.
+There's a surprisingly large amount of interop.
+
+IE9 is tripped up by <xmp>, and also often doesn't add an extra <br> when the
+one it just inserted is extraneous.
+
+Firefox 6.0a2 doesn't notice if you're trying to put the <br> in a bad place,
+which can result in unserializable DOMs.
+
+Chrome 14 dev inserts a literal linebreak for pre and xmp and maybe other
+similar elements.  This doesn't seem very useful, so I don't bother.
+
+Opera 11.11 isn't heedful of <xmp>, and treats <pre> somewhat oddly.
+-->
+
+<ol>
+  <li><span>Delete the contents</span> of the <span>active range</span>.
+
+  <li>If the <span>active range</span>'s [[startnode]] is an [[element]], and
+  "br" is not an <span>allowed child</span> of it, abort these steps.
+  <!-- script, xmp, table, . . . -->
+
+  <li>If the <span>active range</span>'s [[startnode]] is not an [[element]],
+  and "br" is not an <span>allowed child</span> of the <span>active
+  range</span>'s [[startnode]]'s [[parent]], abort these steps.
+
+  <!-- We don't want to call insertNode at the start or end of a text node,
+  because that will leave an empty text node. -->
+  <li>If the <span>active range</span>'s [[startnode]] is a [[text]] node and
+  its [[startoffset]] is zero, set the <span>active range</span>'s
+  [[rangestart]] and [[rangeend]] to ([[parent]] of [[startnode]], [[index]] of
+  [[startnode]]).
+
+  <li>If the <span>active range</span>'s [[startnode]] is a [[text]] node and
+  its [[startoffset]] is the [[length]] of its [[startnode]], set the
+  <span>active range</span>'s [[rangestart]] and [[rangeend]] to ([[parent]] of
+  [[startnode]], 1 + [[index]] of [[startnode]]).
+
+  <li>Let <var>br</var> be the result of calling [[createelement|"br"]] on the
+  [[contextobject]].
+
+  <li>Call [[insertnode|<var>br</var>]] on the <span>active range</span>.
+
+  <li>Call [[selcollapse|]] on the [[contextobject]]'s [[selection]], with
+  <var>br</var>'s [[parent]] as the first argument and one plus <var>br</var>'s
+  [[index]] as the second argument.
+
+  <li>If <var>br</var>'s [[nextsibling]] is null and <var>br</var>'s [[parent]]
+  is not an <span>inline node</span>, or if <var>br</var>'s [[nextsibling]] is
+  not null and not an <span>inline node</span>, call [[createelement|"br"]] on
+  the [[contextobject]] and let <var>extra br</var> be the result, then call
+  [[insertnode|<var>extra br</var>]] on the <span>active range</span>.
+</ol>
+<!-- @} -->
+
 <h3><dfn>The <code title>insertOrderedList</code> command</dfn></h3>
 <!-- @{ -->
 <p><span>Action</span>: <span>Toggle lists</span> with <var>tag name</var>
--- a/tests.js	Sun Jun 19 13:53:21 2011 -0600
+++ b/tests.js	Sun Jun 19 14:39:00 2011 -0600
@@ -1481,6 +1481,10 @@
 		'<div>foo<p>bar[</p></div>]baz',
 	],
 	//@}
+	insertlinebreak: [
+	//@{ Just the same as insertparagraph (set below).
+	],
+	//@}
 	insertorderedlist: [
 	//@{
 		'foo[]bar',
@@ -1726,6 +1730,9 @@
 		'<pre>foo[]&#10;</pre>',
 		'<pre>foo&#10;[]&#10;</pre>',
 
+		'<xmp>foo[]bar</xmp>',
+		'<script>foo[]bar</script>baz',
+
 		'<ol><li>{}<br></li></ol>',
 		'foo<ol><li>{}<br></li></ol>',
 		'<ol><li>{}<br></li></ol>foo',
@@ -1813,6 +1820,7 @@
 		'<a href=/>foo</a>[]bar',
 		'<p>fo[o<p>b]ar',
 		'<p>fo[o<p>bar<p>b]az',
+		'<p>{}<br>',
 	],
 	//@}
 	insertunorderedlist: [
@@ -2803,6 +2811,7 @@
 	],
 	//@}
 };
+tests.insertlinebreak = tests.insertparagraph;
 
 var defaultValues = {
 //@{