Refine formatBlock behavior
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Wed, 25 May 2011 08:51:28 -0600
changeset 166 ee5cecae41d9
parent 165 1fc969f30231
child 167 17f5d951b956
Refine formatBlock behavior

As explained in the lengthy comment I added. Also caught a bug in the
wrap algorithm (not adding <br> when needed), which I fixed, and added
some tests to indent to catch it.
autoimplementation.html
editcommands.html
implementation.js
source.html
--- a/autoimplementation.html	Mon May 23 15:51:33 2011 -0600
+++ b/autoimplementation.html	Wed May 25 08:51:28 2011 -0600
@@ -716,6 +716,16 @@
 		'<p>[foo<blockquote><p>bar</blockquote><p>baz]<p>extra',
 		'<blockquote><p>foo</blockquote><p>[bar]<blockquote><p>baz</blockquote><p>extra',
 
+		'<blockquote>foo[bar]<br>baz</blockquote><p>extra',
+		'<blockquote>foo[bar<br>b]az</blockquote><p>extra',
+		'<blockquote>foo[bar]</blockquote>baz<p>extra',
+		'<blockquote>foo[bar</blockquote>b]az<p>extra',
+		'[foo]<blockquote>bar</blockquote><p>extra',
+		'[foo<blockquote>b]ar</blockquote><p>extra',
+		'foo<blockquote>bar</blockquote>[baz]<p>extra',
+		'[foo<blockquote>bar</blockquote>baz]<p>extra',
+		'<blockquote>foo</blockquote>[bar]<blockquote>baz</blockquote><p>extra',
+
 		// IE:
 		'<blockquote style="margin-right: 0" dir="ltr"><p>foo[bar]</p><p>baz</p></blockquote><p>extra',
 		'<blockquote style="margin-right: 0" dir="ltr"><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
--- a/editcommands.html	Mon May 23 15:51:33 2011 -0600
+++ b/editcommands.html	Wed May 25 08:51:28 2011 -0600
@@ -40,7 +40,7 @@
 <body class=draft>
 <div class=head id=head>
 <h1>HTML Editing Commands</h1>
-<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-23-may-2011>Work in Progress &mdash; Last Update 23 May 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-25-may-2011>Work in Progress &mdash; Last Update 25 May 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;[email protected]&gt;
@@ -482,13 +482,35 @@
   <var title="">node list</var>.
 
   <li>If <var title="">new parent</var> is before the first member of <var title="">node
-  list</var> 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>, then for each <var title="">node</var> in <var title="">node
-  list</var>, append <var title="">node</var> 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="">new
-  parent</var>, <a href=#preserving-ranges>preserving ranges</a>.
-
-  <li>Otherwise, for each <var title="">node</var> in <var title="">node list</var>, <em>in
-  reverse order</em>, insert <var title="">node</var> as the first <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="">new
-  parent</var>, <a href=#preserving-ranges>preserving ranges</a>.
+  list</var> 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>:
+
+  <ol>
+    <li>If 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="">new parent</var> and the first member of
+    <var title="">node list</var> are both <a href=#inline-node title="inline node">inline
+    nodes</a>, and 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="">new parent</var> 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>, 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
+    <code class=external data-anolis-spec=domcore title=dom-Node-ownerDocument><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-ownerdocument>ownerDocument</a></code> of <var title="">new parent</var> 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="">new parent</var>.
+
+    <li>For each <var title="">node</var> in <var title="">node list</var>, append
+    <var title="">node</var> 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="">new parent</var>,
+    <a href=#preserving-ranges>preserving ranges</a>.
+  </ol>
+
+  <li>Otherwise:
+
+  <ol>
+    <li>If the first <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="">new parent</var> and the last member of
+    <var title="">node list</var> are both <a href=#inline-node title="inline node">inline
+    nodes</a>, and the last member of <var title="">node list</var> 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>,
+    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
+    <code class=external data-anolis-spec=domcore title=dom-Node-ownerDocument><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-ownerdocument>ownerDocument</a></code> of <var title="">new parent</var> and insert the result as the
+    first <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="">new parent</var>.
+
+    <li>For each <var title="">node</var> in <var title="">node list</var>, <em>in reverse
+    order</em>, insert <var title="">node</var> as the first <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="">new
+    parent</var>, <a href=#preserving-ranges>preserving ranges</a>.
+  </ol>
 
   <li>If <var title="">original parent</var> is <a href=#editable>editable</a> and 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>, 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>.
@@ -501,6 +523,13 @@
   to merge them in this case. -->
 
   <ol>
+    <li>If <var title="">new parent</var>'s 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> and <var title="">new parent</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>'s first <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> are both <a href=#inline-node title="inline node">inline
+    nodes</a>, and <var title="">new parent</var>'s 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 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>,
+    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
+    <code class=external data-anolis-spec=domcore title=dom-Node-ownerDocument><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-ownerdocument>ownerDocument</a></code> of <var title="">new parent</var> 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="">new parent</var>.
+
     <li>While <var title="">new parent</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> 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>, append
     its first <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> 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="">new parent</var>,
     <a href=#preserving-ranges>preserving ranges</a>.
@@ -620,7 +649,7 @@
 instructions as arguments.  When <code><a href=#querycommandstate()>queryCommandState()</a></code> is invoked,
 the user agent must return the <a href=#state>state</a> for <var title="">command</var>.  When
 <code><a href=#querycommandvalue()>queryCommandValue()</a></code> is invoked, the user agent must return the
-<a href=#value>value</a> for <var title="">command</var> 
+<a href=#value>value</a> for <var title="">command</var>.
 
 <p>Most <a href=#command title=command>commands</a> act on the <dfn id=active-range>active range</dfn>.
 This is defined to be the first <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> in the <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code> given by calling
@@ -2300,9 +2329,9 @@
 <ol>
   <li><a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>, and let <var title="">node
   list</var> be the result.
-  
+
   <li>Let <var title="">state</var> be the <a href=#state>state</a>.
-  
+
   <li><a href=#set-the-value>Set the value</a> of each <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> in <var title="">node list</var> to
   "baseline".
 
@@ -2325,9 +2354,9 @@
 <ol>
   <li><a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>, and let <var title="">node
   list</var> be the result.
-  
+
   <li>Let <var title="">state</var> be the <a href=#state>state</a>.
-  
+
   <li><a href=#set-the-value>Set the value</a> of each <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> in <var title="">node list</var> to
   "baseline".
 
@@ -3520,7 +3549,43 @@
   "header", "hgroup", "li", "nav", "ol", "section", "table", "tbody", "td",
   "th", "thead", "tr", or "ul".
 
-  <li>While <var title="">node list</var> is not empty:
+  <!--
+  We have two different behaviors, one for div and p and one for everything
+  else.  The basic difference is that for div and p, we assume that it should
+  be one line per element, while for other elements, we put in multiple lines
+  separated by <br>.  So if you do formatBlock to p on
+
+    <div>foo</div><div>bar</div> or
+    foo<br>bar
+
+  you get
+
+    <p>foo</p><p>bar</p>
+
+  but formatBlock to h1 will get you
+
+    <h1>foo<br>bar</h1>.
+
+  IE9 will just change the elements as they are, so it gives
+  <p>foo</p><p>bar</p> and <h1>foo</h1><h1>bar</h1> for
+  <div>foo</div><div>bar</div>, but <p>foo<br>bar</p> and <h1>foo<br>bar</h1>
+  for foo<br>bar.  This is unreasonable, but the two possible inputs here look
+  identical to the user and might have been produced by identical user input.
+
+  Firefox 5.0a2 will give results like <p>foo</p><p>bar</p> or
+  <h1>foo</h1><h1>bar</h1> no matter what (modulo oddities in its handling of
+  divs).  Opera 11.10 is similar, except it leaves a trailing <br> in the first
+  element.
+
+  Chrome 13 dev will give results like <p>foo<br>bar</p> or <h1>foo<br>bar</h1>
+  no matter what.
+
+  The specced behavior is a compromise between the existing behaviors,
+  predicated on the fact that <h1>foo</h1><h1>bar</h1> almost never makes
+  sense, and <p>foo<br>bar</p> isn't usually what's wanted either.
+  -->
+  <li>If <var title="">value</var> is "div" or "p", then while <var title="">node list</var> is
+  not empty:
 
   <ol>
     <li>If the first member of <var title="">node list</var> is a <a href=#single-line-container>single-line
@@ -3528,25 +3593,6 @@
     <var title="">node list</var> to <var title="">value</var>, then remove the first member from
     <var title="">node list</var> and continue this loop from the beginning.
 
-    <div class=XXX>
-    <p>This implies that
-
-    </p><xmp><p>foo</p><p>bar</p></xmp>
-
-    <p>becomes (for instance)
-
-    </p><xmp><h1>foo</h1><h1>bar</h1></xmp>
-
-    <p>even though
-
-    </p><xmp><h1>foo<br>bar</h1></xmp>
-
-    <p>would probably make more sense.  WebKit has the latter behavior in all
-    cases.  Perhaps we should adopt WebKit's behavior for things where it
-    rarely makes sense to have two in a row, while keeping the current behavior
-    for divs and paragraphs?
-    </div>
-
     <li>Let <var title="">sublist</var> be an empty list of <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>.
 
     <li>Remove the first member of <var title="">node list</var> and append it to
@@ -3564,6 +3610,45 @@
     returning the result of running <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(<var title="">value</var>)</a></code> on
     the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>.
   </ol>
+
+  <li>Otherwise, while <var title="">node list</var> is not empty:
+
+  <ol>
+    <li>If the first member of <var title="">node list</var> is a <a href=#single-line-container>single-line
+    container</a>:
+
+    <ol>
+      <li>Let <var title="">sublist</var> be the <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> of the first member of
+      <var title="">node list</var>.
+
+      <li>Remove the first member of <var title="">node list</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>,
+      <a href=#preserving-its-descendants>preserving its descendants</a>.
+
+      <li>Remove the first member from <var title="">node list</var>.
+    </ol>
+
+    <li>Otherwise:
+
+    <ol>
+      <li>Let <var title="">sublist</var> be an empty list of <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>.
+
+      <li>Remove the first member of <var title="">node list</var> and append it to
+      <var title="">sublist</var>.
+
+      <li>While <var title="">node list</var> is not empty, and the first member of
+      <var title="">node list</var> is the <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> of the last member of
+      <var title="">sublist</var>, and the first member of <var title="">node list</var> is not a
+      <a href=#single-line-container>single-line container</a>, and the last member of
+      <var title="">sublist</var> 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 first member of <var title="">node
+      list</var> and append it to <var title="">sublist</var>.
+    </ol>
+
+    <li><a href=#wrap>Wrap</a> <var title="">sublist</var>, with <a href=#sibling-criteria>sibling
+    criteria</a> matching any <a href=#html-element>HTML element</a> with <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a>
+    <var title="">value</var> and no attributes, and <a href=#new-parent-instructions>new parent
+    instructions</a> returning the result of running <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(<var title="">value</var>)</a></code> on
+    the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>.
+  </ol>
 </ol>
 
 
--- a/implementation.js	Mon May 23 15:51:33 2011 -0600
+++ b/implementation.js	Wed May 25 08:51:28 2011 -0600
@@ -582,17 +582,38 @@
 	// "Let original parent be the parent of the first member of node list."
 	var originalParent = nodeList[0].parentNode;
 
-	// "If new parent is before the first member of node list in tree order,
-	// then for each node in node list, append node as the last child of new
-	// parent, preserving ranges."
+	// "If new parent is before the first member of node list in tree order:"
 	if (isBefore(newParent, nodeList[0])) {
+		// "If the last child of new parent and the first member of node list
+		// are both inline nodes, and the last child of new parent is not a br,
+		// call createElement("br") on the ownerDocument of new parent and
+		// append the result as the last child of new parent."
+		if (isInlineNode(newParent.lastChild)
+		&& isInlineNode(nodeList[0])
+		&& !isHtmlElement(newParent.lastChild, "BR")) {
+			newParent.appendChild(newParent.ownerDocument.createElement("br"));
+		}
+
+		// "For each node in node list, append node as the last child of new
+		// parent, preserving ranges."
 		for (var i = 0; i < nodeList.length; i++) {
 			movePreservingRanges(nodeList[i], newParent, -1);
 		}
 
-	// "Otherwise, for each node in node list, in reverse order, insert node as
-	// the first child of new parent, preserving ranges."
+	// "Otherwise:"
 	} else {
+		// "If the first child of new parent and the last member of node list
+		// are both inline nodes, and the last member of node list is not a br,
+		// call createElement("br") on the ownerDocument of new parent and
+		// insert the result as the first child of new parent."
+		if (isInlineNode(newParent.firstChild)
+		&& isInlineNode(nodeList[nodeList.length - 1])
+		&& !isHtmlElement(nodeList[nodeList.length - 1], "BR")) {
+			newParent.insertBefore(newParent.ownerDocument.createElement("br"), newParent.firstChild);
+		}
+
+		// "For each node in node list, in reverse order, insert node as the
+		// first child of new parent, preserving ranges."
 		for (var i = nodeList.length - 1; i >= 0; i--) {
 			movePreservingRanges(nodeList[i], newParent, 0);
 		}
@@ -608,6 +629,16 @@
 	// criteria:"
 	if (isEditable(newParent.nextSibling)
 	&& siblingCriteria(newParent.nextSibling)) {
+		// "If new parent's last child and new parent's nextSibling's first
+		// child are both inline nodes, and new parent's last child is not a
+		// br, call createElement("br") on the ownerDocument of new parent and
+		// append the result as the last child of new parent."
+		if (isInlineNode(newParent.lastChild)
+		&& isInlineNode(newParent.nextSibling.firstChild)
+		&& !isHtmlElement(newParent.lastChild, "BR")) {
+			newParent.appendChild(newParent.ownerDocument.createElement("br"));
+		}
+
 		// "While new parent's nextSibling has children, append its first child
 		// as the last child of new parent, preserving ranges."
 		while (newParent.nextSibling.hasChildNodes()) {
@@ -2492,43 +2523,97 @@
 				"TBODY", "TD", "TH", "THEAD", "TR", "UL"]);
 		});
 
-		// "While node list is not empty:"
-		while (nodeList.length) {
-			// "If the first member of node list is a single-line container,
-			// set the tag name of the first member of node list to value, then
-			// remove the first member from node list and continue this loop
-			// from the beginning."
-			if (isHtmlElement(nodeList[0], singleLineContainerNames)) {
-				setTagName(nodeList[0], value);
-				nodeList.shift();
-				continue;
+		// "If value is "div" or "p", then while node list is not empty:"
+		if (value == "div" || value == "p") {
+			while (nodeList.length) {
+				// "If the first member of node list is a single-line
+				// container, set the tag name of the first member of node list
+				// to value, then remove the first member from node list and
+				// continue this loop from the beginning."
+				if (isHtmlElement(nodeList[0], singleLineContainerNames)) {
+					setTagName(nodeList[0], value);
+					nodeList.shift();
+					continue;
+				}
+
+				// "Let sublist be an empty list of nodes."
+				var sublist = [];
+
+				// "Remove the first member of node list and append it to
+				// sublist."
+				sublist.push(nodeList.shift());
+
+				// "While node list is not empty, and the first member of node
+				// list is the nextSibling of the last member of sublist, and
+				// the first member of node list is not a single-line
+				// container, and the last member of sublist is not a br,
+				// remove the first member of node list and append it to
+				// sublist."
+				while (nodeList.length
+				&& nodeList[0] == sublist[sublist.length - 1].nextSibling
+				&& !isHtmlElement(nodeList[0], singleLineContainerNames)
+				&& !isHtmlElement(sublist[sublist.length - 1], "BR")) {
+					sublist.push(nodeList.shift());
+				}
+
+				// "Wrap sublist, with sibling criteria matching nothing and
+				// new parent instructions returning the result of running
+				// createElement(value) on the context object."
+				wrap(sublist,
+					function() { return false },
+					function() { return document.createElement(value) });
 			}
 
-			// "Let sublist be an empty list of nodes."
-			var sublist = [];
-
-			// "Remove the first member of node list and append it to
-			// sublist."
-			sublist.push(nodeList.shift());
-
-			// "While node list is not empty, and the first member of node list
-			// is the nextSibling of the last member of sublist, and the first
-			// member of node list is not a single-line container, and the last
-			// member of sublist is not a br, remove the first member of node
-			// list and append it to sublist."
-			while (nodeList.length
-			&& nodeList[0] == sublist[sublist.length - 1].nextSibling
-			&& !isHtmlElement(nodeList[0], singleLineContainerNames)
-			&& !isHtmlElement(sublist[sublist.length - 1], "BR")) {
-				sublist.push(nodeList.shift());
+		// "Otherwise, while node list is not empty:"
+		} else {
+			while (nodeList.length) {
+				var sublist;
+
+				// "If the first member of node list is a single-line
+				// container:"
+				if (isHtmlElement(nodeList[0], singleLineContainerNames)) {
+					// "Let sublist be the children of the first member of node
+					// list."
+					sublist = [].slice.call(nodeList[0].childNodes);
+
+					// "Remove the first member of node list from its parent,
+					// preserving its descendants."
+					removePreservingDescendants(nodeList[0]);
+
+					// "Remove the first member from node list."
+					nodeList.shift();
+
+				// "Otherwise:"
+				} else {
+					// "Let sublist be an empty list of nodes."
+					sublist = [];
+
+					// "Remove the first member of node list and append it to
+					// sublist."
+					sublist.push(nodeList.shift());
+
+					// "While node list is not empty, and the first member of
+					// node list is the nextSibling of the last member of
+					// sublist, and the first member of node list is not a
+					// single-line container, and the last member of sublist is
+					// not a br, remove the first member of node list and
+					// append it to sublist."
+					while (nodeList.length
+					&& nodeList[0] == sublist[sublist.length - 1].nextSibling
+					&& !isHtmlElement(nodeList[0], singleLineContainerNames)
+					&& !isHtmlElement(sublist[sublist.length - 1], "BR")) {
+						sublist.push(nodeList.shift());
+					}
+				}
+
+				// "Wrap sublist, with sibling criteria matching any HTML
+				// element with local name value and no attributes, and new
+				// parent instructions returning the result of running
+				// createElement(value) on the context object."
+				wrap(sublist,
+					function(node) { return isHtmlElement(node, value.toUpperCase()) && !node.attributes.length },
+					function() { return document.createElement(value) });
 			}
-
-			// "Wrap sublist, with sibling criteria matching nothing and
-			// new parent instructions returning the result of running
-			// createElement(value) on the context object."
-			wrap(sublist,
-				function() { return false },
-				function() { return document.createElement(value) });
 		}
 		break;
 
--- a/source.html	Mon May 23 15:51:33 2011 -0600
+++ b/source.html	Wed May 25 08:51:28 2011 -0600
@@ -442,13 +442,37 @@
   <var>node list</var>.
 
   <li>If <var>new parent</var> is before the first member of <var>node
-  list</var> in [[treeorder]], then for each <var>node</var> in <var>node
-  list</var>, append <var>node</var> as the last [[child]] of <var>new
-  parent</var>, <span>preserving ranges</span>.
-
-  <li>Otherwise, for each <var>node</var> in <var>node list</var>, <em>in
-  reverse order</em>, insert <var>node</var> as the first [[child]] of <var>new
-  parent</var>, <span>preserving ranges</span>.
+  list</var> in [[treeorder]]:
+
+  <ol>
+    <li>If the last [[child]] of <var>new parent</var> and the first member of
+    <var>node list</var> are both <span title="inline node">inline
+    nodes</span>, and the last [[child]] of <var>new parent</var> is not a
+    [[br]], call <code data-anolis-spec=domcore
+    title=dom-Document-createElement>createElement("br")</code> on the
+    [[ownerdocument]] of <var>new parent</var> and append the result as the
+    last [[child]] of <var>new parent</var>.
+
+    <li>For each <var>node</var> in <var>node list</var>, append
+    <var>node</var> as the last [[child]] of <var>new parent</var>,
+    <span>preserving ranges</span>.
+  </ol>
+
+  <li>Otherwise:
+
+  <ol>
+    <li>If the first [[child]] of <var>new parent</var> and the last member of
+    <var>node list</var> are both <span title="inline node">inline
+    nodes</span>, and the last member of <var>node list</var> is not a [[br]],
+    call <code data-anolis-spec=domcore
+    title=dom-Document-createElement>createElement("br")</code> on the
+    [[ownerdocument]] of <var>new parent</var> and insert the result as the
+    first [[child]] of <var>new parent</var>.
+
+    <li>For each <var>node</var> in <var>node list</var>, <em>in reverse
+    order</em>, insert <var>node</var> as the first [[child]] of <var>new
+    parent</var>, <span>preserving ranges</span>.
+  </ol>
 
   <li>If <var>original parent</var> is <span>editable</span> and has no
   [[children]], remove it from its [[parent]].
@@ -461,6 +485,14 @@
   to merge them in this case. -->
 
   <ol>
+    <li>If <var>new parent</var>'s last [[child]] and <var>new parent</var>'s
+    [[nextsibling]]'s first [[child]] are both <span title="inline node">inline
+    nodes</span>, and <var>new parent</var>'s last [[child]] is not a [[br]],
+    call <code data-anolis-spec=domcore
+    title=dom-Document-createElement>createElement("br")</code> on the
+    [[ownerdocument]] of <var>new parent</var> and append the result as the
+    last [[child]] of <var>new parent</var>.
+
     <li>While <var>new parent</var>'s [[nextsibling]] has [[children]], append
     its first [[child]] as the last [[child]] of <var>new parent</var>,
     <span>preserving ranges</span>.
@@ -583,7 +615,7 @@
 instructions as arguments.  When <code>queryCommandState()</code> is invoked,
 the user agent must return the <span>state</span> for <var>command</var>.  When
 <code>queryCommandValue()</code> is invoked, the user agent must return the
-<span>value</span> for <var>command</var> 
+<span>value</span> for <var>command</var>.
 
 <p>Most <span title=command>commands</span> act on the <dfn>active range</dfn>.
 This is defined to be the first [[range]] in the [[selection]] given by calling
@@ -2301,9 +2333,9 @@
 <ol>
   <li><span>Decompose</span> the <span>active range</span>, and let <var>node
   list</var> be the result.
-  
+
   <li>Let <var>state</var> be the <span>state</span>.
-  
+
   <li><span>Set the value</span> of each [[node]] in <var>node list</var> to
   "baseline".
 
@@ -2326,9 +2358,9 @@
 <ol>
   <li><span>Decompose</span> the <span>active range</span>, and let <var>node
   list</var> be the result.
-  
+
   <li>Let <var>state</var> be the <span>state</span>.
-  
+
   <li><span>Set the value</span> of each [[node]] in <var>node list</var> to
   "baseline".
 
@@ -3547,7 +3579,43 @@
   "header", "hgroup", "li", "nav", "ol", "section", "table", "tbody", "td",
   "th", "thead", "tr", or "ul".
 
-  <li>While <var>node list</var> is not empty:
+  <!--
+  We have two different behaviors, one for div and p and one for everything
+  else.  The basic difference is that for div and p, we assume that it should
+  be one line per element, while for other elements, we put in multiple lines
+  separated by <br>.  So if you do formatBlock to p on
+
+    <div>foo</div><div>bar</div> or
+    foo<br>bar
+
+  you get
+
+    <p>foo</p><p>bar</p>
+
+  but formatBlock to h1 will get you
+
+    <h1>foo<br>bar</h1>.
+
+  IE9 will just change the elements as they are, so it gives
+  <p>foo</p><p>bar</p> and <h1>foo</h1><h1>bar</h1> for
+  <div>foo</div><div>bar</div>, but <p>foo<br>bar</p> and <h1>foo<br>bar</h1>
+  for foo<br>bar.  This is unreasonable, but the two possible inputs here look
+  identical to the user and might have been produced by identical user input.
+
+  Firefox 5.0a2 will give results like <p>foo</p><p>bar</p> or
+  <h1>foo</h1><h1>bar</h1> no matter what (modulo oddities in its handling of
+  divs).  Opera 11.10 is similar, except it leaves a trailing <br> in the first
+  element.
+
+  Chrome 13 dev will give results like <p>foo<br>bar</p> or <h1>foo<br>bar</h1>
+  no matter what.
+
+  The specced behavior is a compromise between the existing behaviors,
+  predicated on the fact that <h1>foo</h1><h1>bar</h1> almost never makes
+  sense, and <p>foo<br>bar</p> isn't usually what's wanted either.
+  -->
+  <li>If <var>value</var> is "div" or "p", then while <var>node list</var> is
+  not empty:
 
   <ol>
     <li>If the first member of <var>node list</var> is a <span>single-line
@@ -3555,25 +3623,6 @@
     <var>node list</var> to <var>value</var>, then remove the first member from
     <var>node list</var> and continue this loop from the beginning.
 
-    <div class=XXX>
-    <p>This implies that
-
-    <xmp><p>foo</p><p>bar</p></xmp>
-
-    <p>becomes (for instance)
-
-    <xmp><h1>foo</h1><h1>bar</h1></xmp>
-
-    <p>even though
-
-    <xmp><h1>foo<br>bar</h1></xmp>
-
-    <p>would probably make more sense.  WebKit has the latter behavior in all
-    cases.  Perhaps we should adopt WebKit's behavior for things where it
-    rarely makes sense to have two in a row, while keeping the current behavior
-    for divs and paragraphs?
-    </div>
-
     <li>Let <var>sublist</var> be an empty list of [[nodes]].
 
     <li>Remove the first member of <var>node list</var> and append it to
@@ -3592,6 +3641,47 @@
     title=dom-Document-createElement>createElement(<var>value</var>)</code> on
     the [[contextobject]].
   </ol>
+
+  <li>Otherwise, while <var>node list</var> is not empty:
+
+  <ol>
+    <li>If the first member of <var>node list</var> is a <span>single-line
+    container</span>:
+
+    <ol>
+      <li>Let <var>sublist</var> be the [[children]] of the first member of
+      <var>node list</var>.
+
+      <li>Remove the first member of <var>node list</var> from its [[parent]],
+      <span>preserving its descendants</span>.
+
+      <li>Remove the first member from <var>node list</var>.
+    </ol>
+
+    <li>Otherwise:
+
+    <ol>
+      <li>Let <var>sublist</var> be an empty list of [[nodes]].
+
+      <li>Remove the first member of <var>node list</var> and append it to
+      <var>sublist</var>.
+
+      <li>While <var>node list</var> is not empty, and the first member of
+      <var>node list</var> is the [[nextsibling]] of the last member of
+      <var>sublist</var>, and the first member of <var>node list</var> is not a
+      <span>single-line container</span>, and the last member of
+      <var>sublist</var> is not a [[br]], remove the first member of <var>node
+      list</var> and append it to <var>sublist</var>.
+    </ol>
+
+    <li><span>Wrap</span> <var>sublist</var>, with <span>sibling
+    criteria</span> matching any <span>HTML element</span> with [[localname]]
+    <var>value</var> and no attributes, and <span>new parent
+    instructions</span> returning the result of running <code
+    data-anolis-spec=domcore
+    title=dom-Document-createElement>createElement(<var>value</var>)</code> on
+    the [[contextobject]].
+  </ol>
 </ol>