Support dd and dt in formatBlock
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Sun, 10 Jul 2011 10:51:09 -0600
changeset 381 421087165b64
parent 380 afbcc0a2a352
child 382 08d1d92da268
Support dd and dt in formatBlock

This was significantly more involved than I originally thought. While I
was at it, I wound up changing behavior in some other cases too. For
instance, running insertHTML on "foo[]bar" with value "<dt>baz" now
produces "foo<dl><dt>baz</dt></dl>bar" instead of "foo<p>baz</p>bar".
editcommands.html
implementation.js
preprocess
source.html
tests.js
--- a/editcommands.html	Sun Jul 10 09:40:21 2011 -0600
+++ b/editcommands.html	Sun Jul 10 10:51:09 2011 -0600
@@ -3071,6 +3071,12 @@
   <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestors</a> <a href=#in-the-same-editing-host>in the same editing host</a>:
 
   <ol>
+    <li>If <var title="">node</var> is a <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code>, <a href=#wrap>wrap</a> 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="">node</var>, with <a href=#sibling-criteria>sibling
+    criteria</a> matching any <code class=external data-anolis-spec=html title="the dl element"><a href=http://www.whatwg.org/html/#the-dl-element>dl</a></code> with no <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attribute title=concept-attribute>attributes</a>, and <a href=#new-parent-instructions>new
+    parent instructions</a> returning 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("dl")</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>.  Then abort these steps.
+
     <li>If <var title="">node</var> is not a <a href=#prohibited-paragraph-child>prohibited paragraph child</a>,
     abort these steps.
 
@@ -3881,7 +3887,8 @@
 
 <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
 descendants</dfn>, <a href=#split-the-parent>split the parent</a> of <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-child title=concept-tree-child>children</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>children</a> if it has any.  If it 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>, instead 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>.
 
 
 <h3 id=canonical-space-sequences><span class=secno>8.6 </span>Canonical space sequences</h3>
@@ -5121,6 +5128,18 @@
     <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="">node</var>.
 
+    <li>If <var title="">node</var> is a <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code>, and it is not an
+    <a href=#allowed-child>allowed child</a> of any of its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestors</a> <a href=#in-the-same-editing-host>in the same
+    editing host</a>, <a href=#set-the-tag-name>set the tag name</a> of <var title="">node</var> to
+    the <a href=#default-single-line-container-name>default single-line container name</a> and let <var title="">node</var>
+    be the result.
+    <!--
+    Annoying hack to prevent the dl from being re-added when fixing disallowed
+    ancestors.  In most cases we want a wrapper dl added, but in two cases
+    (delete and insertParagraph) we're actually trying to outdent the list
+    item.  There might be a better way to do this.
+    -->
+
     <li><a href=#fix-disallowed-ancestors>Fix disallowed ancestors</a> of <var title="">node</var>.
 
     <li>Abort these steps.
@@ -5332,11 +5351,8 @@
   <li>Let <var title="">value</var> be <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#converted-to-ascii-lowercase>converted to
   ASCII lowercase</a>.
 
-  <li>If <var title="">value</var> is not "address", "div", "h1", "h2", "h3", "h4",
-  "h5", "h6", "p", or "pre", raise a <code class=external data-anolis-spec=domcore title=dom-DOMException-SYNTAX_ERR><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-domexception-syntax_err>SYNTAX_ERR</a></code> exception.
-
-  <p class=XXX>We should support dt and dd here, since they make sense here and
-  there's no other way to create them.
+  <li>If <var title="">value</var> is not "address", "dd", "div", "dt", "h1", "h2",
+  "h3", "h4", "h5", "h6", "p", or "pre", raise a <code class=external data-anolis-spec=domcore title=dom-DOMException-SYNTAX_ERR><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-domexception-syntax_err>SYNTAX_ERR</a></code> exception.
   <!--
   Opera 11.10 throws NOT_SUPPORTED_ERR for bad elements, all other tested
   browsers ignore the input.  Testing in IE9, Firefox 4.0, Chrome 13 dev, and
@@ -5367,15 +5383,16 @@
   <a href=#editable>editable</a>, the last member of <var title="">original node list</var> (if
   any) is not an <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of <var title="">node</var>, <var title="">node</var> is either a
   <a href=#non-list-single-line-container>non-list single-line container</a> or an <a href=#allowed-child>allowed child</a>
-  of "p", and <var title="">node</var> is not the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of a <a href=#prohibited-paragraph-child>prohibited
-  paragraph child</a>.
+  of "p" or a <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code>, and <var title="">node</var> is not the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of
+  a <a href=#prohibited-paragraph-child>prohibited paragraph child</a>.
 
   <li>For each <var title="">node</var> in <var title="">node list</var>, while <var title="">node</var>
   is the <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> of an <a href=#editable>editable</a> <a href=#html-element>HTML element</a>
   <a href=#in-the-same-editing-host>in the same editing host</a>, which has <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> "address",
-  "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre", and which is not
-  the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of a <a href=#prohibited-paragraph-child>prohibited paragraph child</a>, <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="">node</var>.
+  "dd", "div", "dt", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre", and
+  which is not the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of a <a href=#prohibited-paragraph-child>prohibited paragraph child</a>,
+  <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="">node</var>.
   <!--
   This tries to avoid misnesting if only some lines of an element are selected,
   so <h1>[foo]<br>bar</h1> becomes <p>[foo]</p><h1>bar</h1> instead of
@@ -5409,8 +5426,9 @@
   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.
+  for foo<br>bar.  This is unreasonable, because 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
@@ -5424,46 +5442,27 @@
   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:
+  <li>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=#non-list-single-line-container>non-list
-    single-line container</a>, <a href=#set-the-tag-name>set the tag name</a> of the first
-    member of <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.
-
-    <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=#non-list-single-line-container>non-list 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>.
-
-    <li><a href=#wrap>Wrap</a> <var title="">sublist</var>, with <a href=#sibling-criteria>sibling
-    criteria</a> matching nothing 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>.  Then <a href=#fix-disallowed-ancestors>fix disallowed ancestors</a> of the
-    result.
-    <!-- It's possible to have disallowed ancestors in obscure corner cases,
-    where a node is an allowed child of a p but p is not an allowed child of
-    the node's parent and the node's parent is also not one of the whitelisted
-    elements but is a block element.  <xmp>[foo]</xmp> is the only case I can
-    think of. -->
-  </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=#non-list-single-line-container>non-list
-    single-line container</a>:
+    <li>If the first member of <var title="">node list</var> is a <a href=#single-line-container>single-line
+    container</a>:
 
     <ol>
+      <!--
+      If you try to format a single-line container with no children, IE10PP2
+      inserts an nbsp before formatting.  (It uses nbsp instead of <br> to make
+      blocks not collapse, so the equivalent for us would be to insert a <br>.)
+      Firefox 7.0a2 and Opera 11.50 make the element disappear.  Chrome 14 dev
+      leaves it alone and doesn't format it.  I follow Firefox/Opera just
+      because it's the simplest given how I happen to have written the spec,
+      and it's a corner case, so exact behavior isn't important.
+
+      For blocks that contain only a collapsed whitespace node, IE10PP2 and
+      Firefox 7.0a2 convert them like normal.  Chrome 14 dev and Opera 11.50
+      leave it alone and don't format it.  I go with the majority, which is
+      again simpler to spec.
+      -->
       <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>.
 
@@ -5484,16 +5483,16 @@
       <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=#non-list-single-line-container>non-list single-line container</a>, and the last member of
+      <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>.  Then
+    <li><a href=#wrap>Wrap</a> <var title="">sublist</var>.  If <var title="">value</var> is "div" or
+    "p", <a href=#sibling-criteria>sibling criteria</a> match nothing; otherwise they match 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.  <a href=#new-parent-instructions>New parent instructions</a> return 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>.  Then
     <a href=#fix-disallowed-ancestors>fix disallowed ancestors</a> of the result.
   </ol>
 </ol>
@@ -6262,6 +6261,18 @@
     <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>If <var title="">container</var> is a <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code>, and it is not an
+    <a href=#allowed-child>allowed child</a> of any of its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestors</a> <a href=#in-the-same-editing-host>in the same
+    editing host</a>, <a href=#set-the-tag-name>set the tag name</a> of <var title="">container</var>
+    to the <a href=#default-single-line-container-name>default single-line container name</a> and let
+    <var title="">container</var> be the result.
+    <!--
+    Annoying hack to prevent the dl from being re-added when fixing disallowed
+    ancestors.  In most cases we want a wrapper dl added, but in two cases
+    (delete and insertParagraph) we're actually trying to outdent the list
+    item.  There might be a better way to do this.
+    -->
+
     <li><a href=#fix-disallowed-ancestors>Fix disallowed ancestors</a> of <var title="">container</var>.
 
     <li>Abort these steps.
--- a/implementation.js	Sun Jul 10 09:40:21 2011 -0600
+++ b/implementation.js	Sun Jul 10 10:51:09 2011 -0600
@@ -3419,14 +3419,21 @@
 
 	// "If node is not an allowed child of any of its ancestors in the same
 	// editing host:"
-	var hasAllowedAncestor = false;
-	for (var ancestor = node.parentNode; inSameEditingHost(node, ancestor); ancestor = ancestor.parentNode) {
-		if (isAllowedChild(node, ancestor)) {
-			hasAllowedAncestor = true;
-			break;
-		}
-	}
-	if (!hasAllowedAncestor) {
+	if (getAncestors(node).every(function(ancestor) {
+		return !inSameEditingHost(node, ancestor)
+			|| !isAllowedChild(node, ancestor)
+	})) {
+		// "If node is a dd or dt, wrap the one-node list consisting of node,
+		// with sibling criteria matching any dl with no attributes, and new
+		// parent instructions returning the result of calling
+		// createElement("dl") on the context object. Then abort these steps."
+		if (isHtmlElement(node, ["dd", "dt"])) {
+			wrap([node],
+				function(sibling) { return isHtmlElement(sibling, "dl") && !sibling.attributes.length },
+				function() { return document.createElement("dl") });
+			return;
+		}
+
 		// "If node is not a prohibited paragraph child, abort these steps."
 		if (!isProhibitedParagraphChild(node)) {
 			return;
@@ -4352,9 +4359,14 @@
 }
 
 // "To remove a node node while preserving its descendants, split the parent of
-// node's children."
+// node's children if it has any. If it has no children, instead remove it from
+// its parent."
 function removePreservingDescendants(node) {
-	splitParent([].slice.call(node.childNodes));
+	if (node.hasChildNodes()) {
+		splitParent([].slice.call(node.childNodes));
+	} else {
+		node.parentNode.removeChild(node);
+	}
 }
 
 //@}
@@ -5281,6 +5293,18 @@
 			// "Split the parent of the one-node list consisting of node."
 			splitParent([node]);
 
+			// "If node is a dd or dt, and it is not an allowed child of any of
+			// its ancestors in the same editing host, set the tag name of node
+			// to the default single-line container name and let node be the
+			// result."
+			if (isHtmlElement(node, ["dd", "dt"])
+			&& getAncestors(node).every(function(ancestor) {
+				return !inSameEditingHost(node, ancestor)
+					|| !isAllowedChild(node, ancestor)
+			})) {
+				node = setTagName(node, defaultSingleLineContainerName);
+			}
+
 			// "Fix disallowed ancestors of node."
 			fixDisallowedAncestors(node);
 
@@ -5457,10 +5481,10 @@
 		// "Let value be converted to ASCII lowercase."
 		value = value.toLowerCase();
 
-		// "If value is not "address", "div", "h1", "h2", "h3", "h4", "h5",
-		// "h6", "p", or "pre", raise a SYNTAX_ERR exception."
-		if (["ADDRESS", "DIV", "H1", "H2", "H3", "H4", "H5", "H6", "P",
-		"PRE"].indexOf(value.toUpperCase()) == -1) {
+		// "If value is not "address", "dd", "div", "dt", "h1", "h2", "h3",
+		// "h4", "h5", "h6", "p", or "pre", raise a SYNTAX_ERR exception."
+		if (["address", "dd", "div", "dt", "h1", "h2", "h3", "h4", "h5", "h6",
+		"p", "pre"].indexOf(value) == -1) {
 			throw "SYNTAX_ERR";
 		}
 
@@ -5472,125 +5496,85 @@
 		// "For each node node contained in new range, append node to node list
 		// if it is editable, the last member of original node list (if any) is
 		// not an ancestor of node, node is either a non-list single-line
-		// container or an allowed child of "p", and node is not the ancestor
-		// of a prohibited paragraph child."
+		// container or an allowed child of "p" or a dd or dt, and node is not
+		// the ancestor of a prohibited paragraph child."
 		var nodeList = getContainedNodes(newRange, function(node) {
 			return isEditable(node)
 				&& (isNonListSingleLineContainer(node)
-				|| isAllowedChild(node, "p"))
+				|| isAllowedChild(node, "p")
+				|| isHtmlElement(node, ["dd", "dt"]))
 				&& !getDescendants(node).some(isProhibitedParagraphChild);
 		});
 
 		// "For each node in node list, while node is the descendant of an
 		// editable HTML element in the same editing host, which has local name
-		// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre",
-		// and which is not the ancestor of a prohibited paragraph child, split
-		// the parent of the one-node list consisting of node."
+		// "address", "dd", "div", "dt", "h1", "h2", "h3", "h4", "h5", "h6",
+		// "p", or "pre", and which is not the ancestor of a prohibited
+		// paragraph child, split the parent of the one-node list consisting of
+		// node."
 		for (var i = 0; i < nodeList.length; i++) {
 			var node = nodeList[i];
 			while (getAncestors(node).some(function(ancestor) {
 				return isEditable(ancestor)
 					&& inSameEditingHost(ancestor, node)
-					&& isHtmlElement(ancestor, ["address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"])
+					&& isHtmlElement(ancestor, ["address", "dd", "div", "dt", "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"])
 					&& !getDescendants(ancestor).some(isProhibitedParagraphChild);
 			})) {
 				splitParent([node]);
 			}
 		}
 
-		// "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 non-list 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 (isNonListSingleLineContainer(nodeList[0])) {
-					setTagName(nodeList[0], value);
-					nodeList.shift();
-					continue;
-				}
-
+		// "While node list is not empty:"
+		while (nodeList.length) {
+			var sublist;
+
+			// "If the first member of node list is a single-line
+			// container:"
+			if (isSingleLineContainer(nodeList[0])) {
+				// "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."
-				var sublist = [];
+				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 non-list 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 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
-				&& !isNonListSingleLineContainer(nodeList[0])
+				&& !isSingleLineContainer(nodeList[0])
 				&& !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. Then fix
-				// disallowed ancestors of the result."
-				fixDisallowedAncestors(wrap(sublist,
-					function() { return false },
-					function() { return document.createElement(value) }));
 			}
 
-		// "Otherwise, while node list is not empty:"
-		} else {
-			while (nodeList.length) {
-				var sublist;
-
-				// "If the first member of node list is a non-list single-line
-				// container:"
-				if (isNonListSingleLineContainer(nodeList[0])) {
-					// "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
-					// non-list 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
-					&& !isNonListSingleLineContainer(nodeList[0])
-					&& !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. Then fix
-				// disallowed ancestors of the result."
-				fixDisallowedAncestors(wrap(sublist,
-					function(node) { return isHtmlElement(node, value.toUpperCase()) && !node.attributes.length },
-					function() { return document.createElement(value) }));
-			}
+			// "Wrap sublist. If value is "div" or "p", sibling criteria match
+			// nothing; otherwise they match any HTML element with local name
+			// value and no attributes. New parent instructions return the
+			// result of running createElement(value) on the context object.
+			// Then fix disallowed ancestors of the result."
+			fixDisallowedAncestors(wrap(sublist,
+				["div", "p"].indexOf(value) == - 1
+					? function(node) { return isHtmlElement(node, value) && !node.attributes.length }
+					: function() { return false },
+				function() { return document.createElement(value) }));
 		}
 	}, indeterm: function() {
 		// "Block-extend the active range, and let new range be the result."
@@ -6357,6 +6341,25 @@
 			// "Split the parent of the one-node list consisting of container."
 			splitParent([container]);
 
+			// "If container has no children, call createElement("br") on the
+			// context object and append the result as the last child of
+			// container."
+			if (!container.hasChildNodes()) {
+				container.appendChild(document.createElement("br"));
+			}
+
+			// "If container is a dd or dt, and it is not an allowed child of
+			// any of its ancestors in the same editing host, set the tag name
+			// of container to the default single-line container name and let
+			// container be the result."
+			if (isHtmlElement(container, ["dd", "dt"])
+			&& getAncestors(container).every(function(ancestor) {
+				return !inSameEditingHost(container, ancestor)
+					|| !isAllowedChild(container, ancestor)
+			})) {
+				container = setTagName(container, defaultSingleLineContainerName);
+			}
+
 			// "Fix disallowed ancestors of container."
 			fixDisallowedAncestors(container);
 
--- a/preprocess	Sun Jul 10 09:40:21 2011 -0600
+++ b/preprocess	Sun Jul 10 10:51:09 2011 -0600
@@ -10,6 +10,7 @@
     'a': '<code data-anolis-spec=html title="the a element">a</code>',
     'ancestor': '<span data-anolis-spec=domcore title=concept-tree-ancestor>ancestor</span>',
     'ancestorcontainer': '<span data-anolis-spec=domrange title="ancestor container">ancestor container</span>',
+    'attribute': '<span data-anolis-spec=domcore title=concept-attribute>attribute</span>',
     'attrlocalname': '<span data-anolis-spec=domcore title=concept-attribute-local-name>local name</span>',
     'attrvalue': '<span data-anolis-spec=domcore title=concept-attribute-value>value</span>',
     'b': '<code data-anolis-spec=html title="the b element">b</code>',
@@ -144,7 +145,7 @@
     s = re.sub(r"\[\[" + key + "\|([^]]*)\]\]", fnreplace[key], s)
 
 if "[[" in s:
-    raise Exception("Something mistyped?  " + s[s.find("[["):s.find("]]") + 2])
+    raise Exception("Unrecognized macro: " + s[s.find("[["):s.find("]]") + 2])
 
 s = s.replace("<var>", "<var title>")
 
--- a/source.html	Sun Jul 10 09:40:21 2011 -0600
+++ b/source.html	Sun Jul 10 10:51:09 2011 -0600
@@ -3047,6 +3047,12 @@
   [[ancestors]] <span>in the same editing host</span>:
 
   <ol>
+    <li>If <var>node</var> is a [[dd]] or [[dt]], <span>wrap</span> the
+    one-[[node]] list consisting of <var>node</var>, with <span>sibling
+    criteria</span> matching any [[dl]] with no [[attributes]], and <span>new
+    parent instructions</span> returning the result of calling
+    [[createelement|"dl"]] on the [[contextobject]].  Then abort these steps.
+
     <li>If <var>node</var> is not a <span>prohibited paragraph child</span>,
     abort these steps.
 
@@ -3861,7 +3867,8 @@
 
 <p>To remove a [[node]] <var>node</var> while <dfn>preserving its
 descendants</dfn>, <span>split the parent</span> of <var>node</var>'s
-[[children]].
+[[children]] if it has any.  If it has no [[children]], instead remove it from
+its [[parent]].
 <!-- @} -->
 
 <h3>Canonical space sequences</h3>
@@ -5122,6 +5129,18 @@
     <li><span>Split the parent</span> of the one-[[node]] list consisting of
     <var>node</var>.
 
+    <li>If <var>node</var> is a [[dd]] or [[dt]], and it is not an
+    <span>allowed child</span> of any of its [[ancestors]] <span>in the same
+    editing host</span>, <span>set the tag name</span> of <var>node</var> to
+    the <span>default single-line container name</span> and let <var>node</var>
+    be the result.
+    <!--
+    Annoying hack to prevent the dl from being re-added when fixing disallowed
+    ancestors.  In most cases we want a wrapper dl added, but in two cases
+    (delete and insertParagraph) we're actually trying to outdent the list
+    item.  There might be a better way to do this.
+    -->
+
     <li><span>Fix disallowed ancestors</span> of <var>node</var>.
 
     <li>Abort these steps.
@@ -5333,11 +5352,8 @@
   <li>Let <var>value</var> be <span data-anolis-spec=domcore>converted to
   ASCII lowercase</span>.
 
-  <li>If <var>value</var> is not "address", "div", "h1", "h2", "h3", "h4",
-  "h5", "h6", "p", or "pre", raise a [[SYNTAX_ERR]] exception.
-
-  <p class=XXX>We should support dt and dd here, since they make sense here and
-  there's no other way to create them.
+  <li>If <var>value</var> is not "address", "dd", "div", "dt", "h1", "h2",
+  "h3", "h4", "h5", "h6", "p", or "pre", raise a [[SYNTAX_ERR]] exception.
   <!--
   Opera 11.10 throws NOT_SUPPORTED_ERR for bad elements, all other tested
   browsers ignore the input.  Testing in IE9, Firefox 4.0, Chrome 13 dev, and
@@ -5368,15 +5384,16 @@
   <span>editable</span>, the last member of <var>original node list</var> (if
   any) is not an [[ancestor]] of <var>node</var>, <var>node</var> is either a
   <span>non-list single-line container</span> or an <span>allowed child</span>
-  of "p", and <var>node</var> is not the [[ancestor]] of a <span>prohibited
-  paragraph child</span>.
+  of "p" or a [[dd]] or [[dt]], and <var>node</var> is not the [[ancestor]] of
+  a <span>prohibited paragraph child</span>.
 
   <li>For each <var>node</var> in <var>node list</var>, while <var>node</var>
   is the [[descendant]] of an <span>editable</span> <span>HTML element</span>
   <span>in the same editing host</span>, which has [[localname]] "address",
-  "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre", and which is not
-  the [[ancestor]] of a <span>prohibited paragraph child</span>, <span>split
-  the parent</span> of the one-[[node]] list consisting of <var>node</var>.
+  "dd", "div", "dt", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre", and
+  which is not the [[ancestor]] of a <span>prohibited paragraph child</span>,
+  <span>split the parent</span> of the one-[[node]] list consisting of
+  <var>node</var>.
   <!--
   This tries to avoid misnesting if only some lines of an element are selected,
   so <h1>[foo]<br>bar</h1> becomes <p>[foo]</p><h1>bar</h1> instead of
@@ -5410,8 +5427,9 @@
   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.
+  for foo<br>bar.  This is unreasonable, because 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
@@ -5425,46 +5443,27 @@
   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:
+  <li>While <var>node list</var> is not empty:
 
   <ol>
-    <li>If the first member of <var>node list</var> is a <span>non-list
-    single-line container</span>, <span>set the tag name</span> of the first
-    member of <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.
-
-    <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>non-list 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>.
-
-    <li><span>Wrap</span> <var>sublist</var>, with <span>sibling
-    criteria</span> matching nothing and <span>new parent instructions</span>
-    returning the result of running [[createelement|<var>value</var>]] on the
-    [[contextobject]].  Then <span>fix disallowed ancestors</span> of the
-    result.
-    <!-- It's possible to have disallowed ancestors in obscure corner cases,
-    where a node is an allowed child of a p but p is not an allowed child of
-    the node's parent and the node's parent is also not one of the whitelisted
-    elements but is a block element.  <xmp>[foo]</xmp> is the only case I can
-    think of. -->
-  </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>non-list
-    single-line container</span>:
+    <li>If the first member of <var>node list</var> is a <span>single-line
+    container</span>:
 
     <ol>
+      <!--
+      If you try to format a single-line container with no children, IE10PP2
+      inserts an nbsp before formatting.  (It uses nbsp instead of <br> to make
+      blocks not collapse, so the equivalent for us would be to insert a <br>.)
+      Firefox 7.0a2 and Opera 11.50 make the element disappear.  Chrome 14 dev
+      leaves it alone and doesn't format it.  I follow Firefox/Opera just
+      because it's the simplest given how I happen to have written the spec,
+      and it's a corner case, so exact behavior isn't important.
+
+      For blocks that contain only a collapsed whitespace node, IE10PP2 and
+      Firefox 7.0a2 convert them like normal.  Chrome 14 dev and Opera 11.50
+      leave it alone and don't format it.  I go with the majority, which is
+      again simpler to spec.
+      -->
       <li>Let <var>sublist</var> be the [[children]] of the first member of
       <var>node list</var>.
 
@@ -5485,16 +5484,16 @@
       <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>non-list single-line container</span>, and the last member of
+      <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
-    [[createelement|<var>value</var>]] on the [[contextobject]].  Then
+    <li><span>Wrap</span> <var>sublist</var>.  If <var>value</var> is "div" or
+    "p", <span>sibling criteria</span> match nothing; otherwise they match any
+    <span>HTML element</span> with [[localname]] <var>value</var> and no
+    attributes.  <span>New parent instructions</span> return the result of
+    running [[createelement|<var>value</var>]] on the [[contextobject]].  Then
     <span>fix disallowed ancestors</span> of the result.
   </ol>
 </ol>
@@ -6275,6 +6274,18 @@
     [[createelement|"br"]] on the [[contextobject]] and append the result as
     the last [[child]] of <var>container</var>.
 
+    <li>If <var>container</var> is a [[dd]] or [[dt]], and it is not an
+    <span>allowed child</span> of any of its [[ancestors]] <span>in the same
+    editing host</span>, <span>set the tag name</span> of <var>container</var>
+    to the <span>default single-line container name</span> and let
+    <var>container</var> be the result.
+    <!--
+    Annoying hack to prevent the dl from being re-added when fixing disallowed
+    ancestors.  In most cases we want a wrapper dl added, but in two cases
+    (delete and insertParagraph) we're actually trying to outdent the list
+    item.  There might be a better way to do this.
+    -->
+
     <li><span>Fix disallowed ancestors</span> of <var>container</var>.
 
     <li>Abort these steps.
--- a/tests.js	Sun Jul 10 09:40:21 2011 -0600
+++ b/tests.js	Sun Jul 10 10:51:09 2011 -0600
@@ -1905,6 +1905,8 @@
 		'<dl><dt>{}<br></dt></dl>',
 		'<dl><dt>foo<dd>{}<br></dl>',
 		'<dl><dt>{}<br><dd>bar</dl>',
+		'<dl><dt>foo<dd>bar<dl><dt>{}<br><dd>baz</dl></dl>',
+		'<dl><dt>foo<dd>bar<dl><dt>baz<dd>{}<br></dl></dl>',
 
 		'<h1>foo[bar</h1><p>baz]quz</p>',
 		'<p>foo[bar</p><h1>baz]quz</h1>',