Make extraneous line breaks more robust
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Tue, 21 Jun 2011 16:18:46 -0600
changeset 308 fb902b8019df
parent 307 006aba32edd1
child 309 c0fc2d85fb82
Make extraneous line breaks more robust

This fixes some cases where they weren't being removed when they should
have been.
editcommands.html
implementation.js
source.html
--- a/editcommands.html	Tue Jun 21 15:28:14 2011 -0600
+++ b/editcommands.html	Tue Jun 21 16:18:46 2011 -0600
@@ -446,6 +446,19 @@
 Or its ancestor has a fixed height?  Would it be better to use some DOM-based
 definition?
 
+<p>An <dfn id=extraneous-line-break>extraneous line break</dfn> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> that has no visual effect,
+in that removing it from the DOM would not change layout, except that a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>
+that is the sole child of an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> is not extraneous.
+
+<p class=XXX>Also possibly a bad definition.  Again, I test by just removing it
+and seeing what happens.  (Actually, setting display: none, so that it doesn't
+mess up ranges.)
+
+<p class=XXX>The thing about li is a not very nice hack.  The issue is that an
+li won't collapse even if it has no children at all, but that's not true in all
+browsers (at least not in Opera 11.11), and also it breaks assumptions
+elsewhere.  E.g., if it gets turned into a p.
+
 <p>Each <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> has a boolean <dfn id=css-styling-flag>CSS styling flag</dfn> associated
 with it, which must initially be false.  (<a href=#the-stylewithcss-command>The <code title="">styleWithCSS</code> command</a> can be used to modify or query it, by
 means of the <code><a href=#execcommand()>execCommand()</a></code> and <code><a href=#querycommandstate()>queryCommandState()</a></code>
@@ -531,26 +544,38 @@
 <var title="">node</var>:
 
 <ol>
-  <li>If <var title="">node</var> 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>, or it is an <a href=#inline-node>inline
-  node</a>, do nothing and abort these steps.
-
-  <li>If the <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> of <var title="">node</var> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, and the
-  <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> of the <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> of <var title="">node</var> is an
-  <a href=#inline-node>inline node</a> that is not a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, remove the <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code>
-  of <var title="">node</var> from its <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>.
+  <li>Let <var title="">ref</var> be the <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> of <var title="">node</var>.
+
+  <li>If <var title="">ref</var> is null, abort these steps.
+
+  <li>While <var title="">ref</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, set <var title="">ref</var> to its
+  <code class=external data-anolis-spec=domcore title=dom-Node-lastChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-lastchild>lastChild</a></code>.
+
+  <li>While <var title="">ref</var> is an <a href=#invisible-node>invisible node</a> but not an
+  <a href=#extraneous-line-break>extraneous line break</a>, and <var title="">ref</var> does not equal
+  <var title="">node</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>, set <var title="">ref</var> to the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> before it in
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>.
+
+  <li>If <var title="">ref</var> is an <a href=#editable>editable</a> <a href=#extraneous-line-break>extraneous line
+  break</a>, remove it from its <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>.
 </ol>
 
 <p>To <dfn id=remove-extraneous-line-breaks-at-the-end-of>remove extraneous line breaks at the end of</dfn> a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>
 <var title="">node</var>:
 
 <ol>
-  <li>If <var title="">node</var> 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>, or it is an <a href=#inline-node>inline
-  node</a>, do nothing and abort these steps.
-
-  <li>If <var title="">node</var> has at least two <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, and its last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>
-  is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, and its second-to-last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is an <a href=#inline-node>inline node</a>
-  that is not a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, remove the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">node</var> from
-  <var title="">node</var>.
+  <li>Let <var title="">ref</var> be <var title="">node</var>.
+
+  <li>While <var title="">ref</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, set <var title="">ref</var> to its
+  <code class=external data-anolis-spec=domcore title=dom-Node-lastChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-lastchild>lastChild</a></code>.
+
+  <li>While <var title="">ref</var> is an <a href=#invisible-node>invisible node</a> but not an
+  <a href=#extraneous-line-break>extraneous line break</a>, and <var title="">ref</var> does not equal
+  <var title="">node</var>, set <var title="">ref</var> to the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> before it in
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>.
+
+  <li>If <var title="">ref</var> is an <a href=#editable>editable</a> <a href=#extraneous-line-break>extraneous line
+  break</a>, remove it from its <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>.
 </ol>
 
 <p>To <dfn id=remove-extraneous-line-breaks-from>remove extraneous line breaks from</dfn> a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>, first
@@ -675,8 +700,10 @@
   <var title="">original parent</var>.
 
   <li>If <var title="">node list</var>'s last member'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,
-  <a href=#remove-extraneous-line-breaks-at-the-end-of>remove extraneous line breaks at the end of</a> <var title="">node
-  list</var>'s last member'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>.
+  but its <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 null, <a href=#remove-extraneous-line-breaks-at-the-end-of>remove extraneous line breaks at the
+  end of</a> <var title="">node list</var>'s last member'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>.
+  <!-- The parent might be null if it's a br that we removed in the last step,
+  in which case this step isn't necessary. -->
 </ol>
 
 <p>To remove a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> <var title="">node</var> while <dfn id=preserving-its-descendants>preserving its
@@ -2562,9 +2589,9 @@
 <!-- Possibly to be made configurable later. -->
 
 <p>A <dfn id=visible-node>visible node</dfn> is a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> that either is a <a href=#prohibited-paragraph-child>prohibited
-paragraph child</a>, or 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 whose <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> is not empty, or a
-<code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or <code class=external data-anolis-spec=html title="the img element"><a href=http://www.whatwg.org/html/#the-img-element>img</a></code>, or any <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> with a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-descendant title=concept-tree-descendant>descendant</a> that is a
-<a href=#visible-node>visible node</a>.
+paragraph child</a>, or 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 whose <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> is not empty, or
+an <code class=external data-anolis-spec=html title="the img element"><a href=http://www.whatwg.org/html/#the-img-element>img</a></code>, or a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> that is not an <a href=#extraneous-line-break>extraneous line break</a>, or
+any <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> with a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-descendant title=concept-tree-descendant>descendant</a> that is a <a href=#visible-node>visible node</a>.
 
 <p>An <dfn id=invisible-node>invisible node</dfn> is a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> that is not a <a href=#visible-node>visible
 node</a>.
@@ -4903,27 +4930,6 @@
     <var title="">offset</var> and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is an <a href=#editable>editable</a>
     <a href=#invisible-node>invisible node</a>, remove that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> from <var title="">node</var>.
 
-    <!-- We need these extra two steps in forwardDelete, relative to delete, to
-    skip over line breaks at the end of blocks. -->
-    <li>Otherwise, if <var title="">offset</var> is one less than <var title="">node</var>'s
-    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a>, and <var title="">node</var> is a <a href=#prohibited-paragraph-child>prohibited paragraph
-    child</a> whose last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, add one to
-    <var title="">offset</var>.
-
-    <li>Otherwise, if <var title="">node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
-    <var title="">offset</var> + 1, and its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">offset</var> is
-    a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, and its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">offset</var> + 1 is a
-    <a href=#prohibited-paragraph-child>prohibited paragraph child</a>, add one to <var title="">offset</var>.
-
-    <div class=XXX>
-    <p>These two steps don't correctly handle things like
-
-    </p><xmp><p>foo<span><br></span></p></xmp>
-
-    <p>For that matter, nor do most cases where we deal with line breaks like
-    this.  Should add a bunch of tests for this and change stuff so we pass.
-    </div>
-
     <li>Otherwise, if <var title="">offset</var> 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
     <var title="">node</var> and <var title="">node</var> is not a <a href=#prohibited-paragraph-child>prohibited paragraph
     child</a>, or if <var title="">node</var> is an <a href=#invisible-node>invisible node</a>, set
@@ -5527,6 +5533,10 @@
     <li><a href=#split-the-parent>Split the parent</a> of the one-<a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> list consisting of
     <var title="">container</var>.
 
+    <li>If <var title="">container</var> has no <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</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 append the result as
+    the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">container</var>.
+
     <li><a href=#fix-disallowed-ancestors>Fix disallowed ancestors</a> of <var title="">container</var>.
 
     <li>Abort these steps.
--- a/implementation.js	Tue Jun 21 15:28:14 2011 -0600
+++ b/implementation.js	Tue Jun 21 16:18:46 2011 -0600
@@ -683,6 +683,36 @@
 	return origHeight != finalHeight;
 }
 
+// "An extraneous line break is a br that has no visual effect, in that
+// removing it from the DOM would not change layout, except that a br that is
+// the sole child of an li is not extraneous."
+function isExtraneousLineBreak(br) {
+	if (!isHtmlElement(br, "br")) {
+		return false;
+	}
+
+	if (isHtmlElement(br.parentNode, "li")
+	&& br.parentNode.childNodes.length == 1) {
+		return false;
+	}
+
+	var ref = br.parentNode;
+	while (getComputedStyle(ref).display == "inline") {
+		ref = ref.parentNode;
+	}
+	var style = br.hasAttribute("style") ? br.getAttribute("style") : null;
+	var origHeight = ref.offsetHeight;
+	br.setAttribute("style", "display:none");
+	var finalHeight = ref.offsetHeight;
+	if (style === null) {
+		br.removeAttribute("style");
+	} else {
+		br.setAttribute("style", style);
+	}
+
+	return origHeight == finalHeight;
+}
+
 //@}
 
 /////////////////////////////
@@ -806,41 +836,58 @@
 }
 
 function removeExtraneousLineBreaksBefore(node) {
-	// "If node is not an Element, or it is an inline node, do nothing and
-	// abort these steps."
-	if (!node
-	|| node.nodeType != Node.ELEMENT_NODE
-	|| isInlineNode(node)) {
+	// "Let ref be the previousSibling of node."
+	var ref = node.previousSibling;
+
+	// "If ref is null, abort these steps."
+	if (!ref) {
 		return;
 	}
 
-	// "If the previousSibling of node is a br, and the previousSibling of the
-	// previousSibling of node is an inline node that is not a br, remove the
-	// previousSibling of node from its parent."
-	if (isHtmlElement(node.previousSibling, "BR")
-	&& isInlineNode(node.previousSibling.previousSibling)
-	&& !isHtmlElement(node.previousSibling.previousSibling, "BR")) {
-		node.parentNode.removeChild(node.previousSibling);
+	// "While ref has children, set ref to its lastChild."
+	while (ref.hasChildNodes()) {
+		ref = ref.lastChild;
+	}
+
+	// "While ref is an invisible node but not an extraneous line break, and
+	// ref does not equal node's parent, set ref to the node before it in tree
+	// order."
+	while (isInvisibleNode(ref)
+	&& !isExtraneousLineBreak(ref)
+	&& ref != node.parentNode) {
+		ref = previousNode(ref);
+	}
+
+	// "If ref is an editable extraneous line break, remove it from its
+	// parent."
+	if (isEditable(ref)
+	&& isExtraneousLineBreak(ref)) {
+		ref.parentNode.removeChild(ref);
 	}
 }
 
 function removeExtraneousLineBreaksAtTheEndOf(node) {
-	// "If node is not an Element, or it is an inline node, do nothing and
-	// abort these steps."
-	if (!node
-	|| node.nodeType != Node.ELEMENT_NODE
-	|| isInlineNode(node)) {
-		return;
-	}
-
-	// "If node has at least two children, and its last child is a br, and its
-	// second-to-last child is an inline node that is not a br, remove the last
-	// child of node from node."
-	if (node.childNodes.length >= 2
-	&& isHtmlElement(node.lastChild, "BR")
-	&& isInlineNode(node.lastChild.previousSibling)
-	&& !isHtmlElement(node.lastChild.previousSibling, "BR")) {
-		node.removeChild(node.lastChild);
+	// "Let ref be node."
+	var ref = node;
+
+	// "While ref has children, set ref to its lastChild."
+	while (ref.hasChildNodes()) {
+		ref = ref.lastChild;
+	}
+
+	// "While ref is an invisible node but not an extraneous line break, and
+	// ref does not equal node, set ref to the node before it in tree order."
+	while (isInvisibleNode(ref)
+	&& !isExtraneousLineBreak(ref)
+	&& ref != node) {
+		ref = previousNode(ref);
+	}
+
+	// "If ref is an editable extraneous line break, remove it from its
+	// parent."
+	if (isEditable(ref)
+	&& isExtraneousLineBreak(ref)) {
+		ref.parentNode.removeChild(ref);
 	}
 }
 
@@ -956,9 +1003,11 @@
 		removeExtraneousLineBreaksBefore(originalParent);
 	}
 
-	// "If node list's last member's nextSibling is null, remove extraneous
-	// line breaks at the end of node list's last member's parent."
-	if (!nodeList[nodeList.length - 1].nextSibling) {
+	// "If node list's last member's nextSibling is null, but its parent is not
+	// null, remove extraneous line breaks at the end of node list's last
+	// member's parent."
+	if (!nodeList[nodeList.length - 1].nextSibling
+	&& nodeList[nodeList.length - 1].parentNode) {
 		removeExtraneousLineBreaksAtTheEndOf(nodeList[nodeList.length - 1].parentNode);
 	}
 }
@@ -3013,15 +3062,17 @@
 var defaultSingleLineContainerName = "p";
 
 // "A visible node is a node that either is a prohibited paragraph child, or a
-// Text node whose data is not empty, or a br or img, or any node with a
-// descendant that is a visible node."
+// Text node whose data is not empty, or an img, or a br that is not an
+// extraneous line break, or any node with a descendant that is a visible
+// node."
 function isVisibleNode(node) {
 	if (!node) {
 		return false;
 	}
 	if (isProhibitedParagraphChild(node)
 	|| (node.nodeType == Node.TEXT_NODE && node.length)
-	|| isHtmlElement(node, ["br", "img"])) {
+	|| isHtmlElement(node, "img")
+	|| (isHtmlElement(node, "br") && !isExtraneousLineBreak(node))) {
 		return true;
 	}
 	for (var i = 0; i < node.childNodes.length; i++) {
@@ -5134,22 +5185,6 @@
 			&& isInvisibleNode(node.childNodes[offset])) {
 				node.removeChild(node.childNodes[offset]);
 
-			// "Otherwise, if offset is one less than node's length, and node
-			// is a prohibited paragraph child whose last child is a br, add
-			// one to offset."
-			} else if (offset == getNodeLength(node) - 1
-			&& isProhibitedParagraphChild(node)
-			&& isHtmlElement(node.lastChild, "br")) {
-				offset++;
-
-			// "Otherwise, if node has a child with index offset + 1, and its
-			// child of index offset is a br, and its child of index offset + 1
-			// is a prohibited paragraph child, add one to offset."
-			} else if (offset + 1 < node.childNodes.length
-			&& isHtmlElement(node.childNodes[offset], "br")
-			&& isProhibitedParagraphChild(node.childNodes[offset + 1])) {
-				offset++;
-
 			// "Otherwise, if offset is the length of node and node is not a
 			// prohibited paragraph child, or if node is an invisible node, set
 			// offset to one plus the index of node, then set node to its
--- a/source.html	Tue Jun 21 15:28:14 2011 -0600
+++ b/source.html	Tue Jun 21 16:18:46 2011 -0600
@@ -388,6 +388,19 @@
 Or its ancestor has a fixed height?  Would it be better to use some DOM-based
 definition?
 
+<p>An <dfn>extraneous line break</dfn> is a [[br]] that has no visual effect,
+in that removing it from the DOM would not change layout, except that a [[br]]
+that is the sole child of an [[li]] is not extraneous.
+
+<p class=XXX>Also possibly a bad definition.  Again, I test by just removing it
+and seeing what happens.  (Actually, setting display: none, so that it doesn't
+mess up ranges.)
+
+<p class=XXX>The thing about li is a not very nice hack.  The issue is that an
+li won't collapse even if it has no children at all, but that's not true in all
+browsers (at least not in Opera 11.11), and also it breaks assumptions
+elsewhere.  E.g., if it gets turned into a p.
+
 <p>Each [[htmldocument]] has a boolean <dfn>CSS styling flag</dfn> associated
 with it, which must initially be false.  (<span>The <code
 title>styleWithCSS</code> command</span> can be used to modify or query it, by
@@ -476,26 +489,38 @@
 <var>node</var>:
 
 <ol>
-  <li>If <var>node</var> is not an [[element]], or it is an <span>inline
-  node</span>, do nothing and abort these steps.
-
-  <li>If the [[previoussibling]] of <var>node</var> is a [[br]], and the
-  [[previoussibling]] of the [[previoussibling]] of <var>node</var> is an
-  <span>inline node</span> that is not a [[br]], remove the [[previoussibling]]
-  of <var>node</var> from its [[parent]].
+  <li>Let <var>ref</var> be the [[previoussibling]] of <var>node</var>.
+
+  <li>If <var>ref</var> is null, abort these steps.
+
+  <li>While <var>ref</var> has [[children]], set <var>ref</var> to its
+  [[lastchild]].
+
+  <li>While <var>ref</var> is an <span>invisible node</span> but not an
+  <span>extraneous line break</span>, and <var>ref</var> does not equal
+  <var>node</var>'s [[parent]], set <var>ref</var> to the [[node]] before it in
+  [[treeorder]].
+
+  <li>If <var>ref</var> is an <span>editable</span> <span>extraneous line
+  break</span>, remove it from its [[parent]].
 </ol>
 
 <p>To <dfn>remove extraneous line breaks at the end of</dfn> a [[node]]
 <var>node</var>:
 
 <ol>
-  <li>If <var>node</var> is not an [[element]], or it is an <span>inline
-  node</span>, do nothing and abort these steps.
-
-  <li>If <var>node</var> has at least two [[children]], and its last [[child]]
-  is a [[br]], and its second-to-last [[child]] is an <span>inline node</span>
-  that is not a [[br]], remove the last [[child]] of <var>node</var> from
-  <var>node</var>.
+  <li>Let <var>ref</var> be <var>node</var>.
+
+  <li>While <var>ref</var> has [[children]], set <var>ref</var> to its
+  [[lastchild]].
+
+  <li>While <var>ref</var> is an <span>invisible node</span> but not an
+  <span>extraneous line break</span>, and <var>ref</var> does not equal
+  <var>node</var>, set <var>ref</var> to the [[node]] before it in
+  [[treeorder]].
+
+  <li>If <var>ref</var> is an <span>editable</span> <span>extraneous line
+  break</span>, remove it from its [[parent]].
 </ol>
 
 <p>To <dfn>remove extraneous line breaks from</dfn> a [[node]], first
@@ -623,8 +648,10 @@
   <var>original parent</var>.
 
   <li>If <var>node list</var>'s last member's [[nextsibling]] is null,
-  <span>remove extraneous line breaks at the end of</span> <var>node
-  list</var>'s last member's [[parent]].
+  but its [[parent]] is not null, <span>remove extraneous line breaks at the
+  end of</span> <var>node list</var>'s last member's [[parent]].
+  <!-- The parent might be null if it's a br that we removed in the last step,
+  in which case this step isn't necessary. -->
 </ol>
 
 <p>To remove a [[node]] <var>node</var> while <dfn>preserving its
@@ -2541,9 +2568,9 @@
 <!-- Possibly to be made configurable later. -->
 
 <p>A <dfn>visible node</dfn> is a [[node]] that either is a <span>prohibited
-paragraph child</span>, or a [[text]] node whose [[cddata]] is not empty, or a
-[[br]] or [[img]], or any [[node]] with a [[descendant]] that is a
-<span>visible node</span>.
+paragraph child</span>, or a [[text]] node whose [[cddata]] is not empty, or
+an [[img]], or a [[br]] that is not an <span>extraneous line break</span>, or
+any [[node]] with a [[descendant]] that is a <span>visible node</span>.
 
 <p>An <dfn>invisible node</dfn> is a [[node]] that is not a <span>visible
 node</span>.
@@ -4907,27 +4934,6 @@
     <var>offset</var> and that [[child]] is an <span>editable</span>
     <span>invisible node</span>, remove that [[child]] from <var>node</var>.
 
-    <!-- We need these extra two steps in forwardDelete, relative to delete, to
-    skip over line breaks at the end of blocks. -->
-    <li>Otherwise, if <var>offset</var> is one less than <var>node</var>'s
-    [[length]], and <var>node</var> is a <span>prohibited paragraph
-    child</span> whose last [[child]] is a [[br]], add one to
-    <var>offset</var>.
-
-    <li>Otherwise, if <var>node</var> has a [[child]] with [[index]]
-    <var>offset</var> + 1, and its [[child]] of [[index]] <var>offset</var> is
-    a [[br]], and its [[child]] of [[index]] <var>offset</var> + 1 is a
-    <span>prohibited paragraph child</span>, add one to <var>offset</var>.
-
-    <div class=XXX>
-    <p>These two steps don't correctly handle things like
-
-    <xmp><p>foo<span><br></span></p></xmp>
-
-    <p>For that matter, nor do most cases where we deal with line breaks like
-    this.  Should add a bunch of tests for this and change stuff so we pass.
-    </div>
-
     <li>Otherwise, if <var>offset</var> is the [[nodelength]] of
     <var>node</var> and <var>node</var> is not a <span>prohibited paragraph
     child</span>, or if <var>node</var> is an <span>invisible node</span>, set
@@ -5541,6 +5547,10 @@
     <li><span>Split the parent</span> of the one-[[node]] list consisting of
     <var>container</var>.
 
+    <li>If <var>container</var> has no [[children]], call
+    [[createelement|"br"]] on the [[contextobject]] and append the result as
+    the last [[child]] of <var>container</var>.
+
     <li><span>Fix disallowed ancestors</span> of <var>container</var>.
 
     <li>Abort these steps.