Significant cleanup of formatBlock
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Wed, 29 Jun 2011 15:56:26 -0600
changeset 354 c0ea111708e5
parent 353 2e4a02cf573b
child 355 18083af29a07
Significant cleanup of formatBlock

This gets rid of the ugly "fix prohibited paragraph descendants" thing,
as well as a bunch of other logic, and it fixes at least two notable
bugs.

First, running it on <div>[foo]<br>bar</div> will now produce
<p>[foo]</p><div>bar</div>, properly splitting it like all the other
non-list single-line containers.

Second, running it on <div><ol><li>[foo]</li></ol></div> will no longer
produce totally unreasonable markup like <div><li>[foo]</li></div>.
Instead it just ignores the wrapping div, since it has a prohibited
paragraph child descendant, and it produces
<div><ol><li><div>[foo]</li></ol></div>. This prevents unserializable
DOMs in some cases, like with tables.
editcommands.html
implementation.js
source.html
--- a/editcommands.html	Wed Jun 29 14:50:14 2011 -0600
+++ b/editcommands.html	Wed Jun 29 15:56:26 2011 -0600
@@ -3121,12 +3121,14 @@
 
 <h3 id=assorted-block-formatting-command-algorithms><span class=secno>8.2 </span>Assorted block formatting command algorithms</h3>
 
-<p>To <dfn id=fix-disallowed-ancestors>fix disallowed ancestors</dfn> of 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>:
+<p>To <dfn id=fix-disallowed-ancestors>fix disallowed ancestors</dfn> of <var title="">node</var>:
 
 <p class=XXX>Do we want to get rid of attributes that are no longer allowed
 here?
 
 <ol>
+  <li>If <var title="">node</var> is not <a href=#editable>editable</a>, abort these steps.
+
   <li>If <var title="">node</var> 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>:
 
@@ -3154,58 +3156,6 @@
   of <var title="">node</var>.
 </ol>
 
-<p>To <dfn id=fix-prohibited-paragraph-descendants>fix prohibited paragraph descendants</dfn> of 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>:
-
-<p class=note>This will fix any case where a descendant of node is a prohibited
-paragraph child.  The function returns a list consisting of <var title="">node</var> and
-any new siblings this algorithm might give it, if some of its children are
-split out of it.
-
-<p class=XXX>Possibly can be converted to just fix disallowed ancestors of all
-descendant nodes.  The only reason this is nontrivial is because the caller
-needs the list of returned nodes.  (It's only used by formatBlock, but it
-currently has to be a separate function because it's recursive.)
-
-<ol>
-  <li>If <var title="">node</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>, return 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>Let <var title="">children</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 <var title="">node</var>.
-
-  <li><a href=#fix-prohibited-paragraph-descendants>Fix prohibited paragraph descendants</a> of each member of
-  <var title="">children</var>.
-
-  <!-- When we split the parent of a node, three things can happen.  Either the
-  node gets stuck before its parent, or after its parent, or the parent gets
-  copied and the copy gets stuck before it.  So the nodes we want to return are
-  consecutive siblings; the first is either the original first child of node or
-  its parent, and the last is either the last child of node or node itself. -->
-  <li>Let <var title="">children</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 <var title="">node</var>.
-
-  <li>For each <var title="">child</var> in <var title="">children</var>, if <var title="">child</var> is
-  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="">child</var>.
-
-  <li>If <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> is null, let <var title="">node</var> equal the
-  last member of <var title="">children</var>.
-
-  <li>Let <var title="">node list</var> be a 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>, initially empty.
-
-  <li>Repeat these steps:
-
-  <ol>
-    <li>Prepend <var title="">node</var> to <var title="">node list</var>.
-
-    <li>If <var title="">node</var> is <var title="">children</var>'s first member, or
-    <var title="">children</var>'s first 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>, break from this loop.
-
-    <li>Set <var title="">node</var> to its <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>.
-  </ol>
-
-  <li>Return <var title="">node list</var>.
-</ol>
-
 <p>To <dfn id=indent>indent</dfn> a list <var title="">node list</var> of consecutive <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-sibling title=concept-tree-sibling>sibling</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>nodes</a>:
 <!--
@@ -5357,44 +5307,34 @@
   <li><a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>, and let <var title="">new
   range</var> be the result.
 
-  <li>Let <var title="">original node list</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>Let <var title="">node list</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>For 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> <var title="">node</var> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new range</var>,
-  append <var title="">node</var> to <var title="">original node list</var> if it is
+  append <var title="">node</var> to <var title="">node list</var> if it is
   <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>, and <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".
-
-  <li>For each <var title="">node</var> in <var title="">original node list</var>, while either
-  <var title="">node</var> is 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> 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> 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>
-  "address", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre"; or
-  <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> is not null, and "p" is not an <a href=#allowed-child>allowed
-  child</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-parent title=concept-tree-parent>parent</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>.
-
-  <div class=XXX>
-  <p>This is needed so we don't get things like p nested inside address, and
-  instead convert part of the multi-line address into a p.  But div can contain
-  any of these things, so we don't break that apart ever, which is bad.  It
-  means if you have
-
-  </p><xmp><div>foo<br>bar</div></xmp>
-
-  <p>then formatBlocking "foo" then "bar" as p has different results from doing
-  both at once.  Maybe we should split divs as well, but only if they're the
-  parent of the node we're dealing with?  Or just split divs always, and hope
-  the "in the same editing host" thing handles it?
-  </div>
-
-  <p class=XXX>We probably don't want to break out of tables and such here.
-
-  <li>Let <var title="">node list</var> be a 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>, initially empty.
-
-  <li>For each <var title="">node</var> in <var title="">original node list</var>, <a href=#fix-prohibited-paragraph-descendants>fix
-  prohibited paragraph descendants</a> of <var title="">node</var>, and append the
-  resulting <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> to <var title="">node list</var>.
+  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>.
+
+  <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>.
+  <!--
+  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
+  <h1><p>[foo]</p><br>bar</h1> or such.  It tries to heuristically distinguish
+  between divs used as line-breakers and divs used as actual wrappers by
+  checking if they have prohibited paragraph children as descendants.  It works
+  for address too, in case there are paragraphs nested inside.  Thus
+  <address>[foo]<br>bar</address> becomes <p>[foo]</p><address>bar</address>,
+  but <address>[foo]<p>bar</p></address> becomes
+  <address><p>[foo]</p><p>bar</p></address>.  Likewise, we don't break things
+  out of lists or tables or such if they happen to be nested in a <div>.
+  -->
 
   <!--
   We have two different behaviors, one for div and p and one for everything
@@ -5455,7 +5395,13 @@
     <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>.
+    <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:
@@ -5494,7 +5440,8 @@
     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>.
+    <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>
 
@@ -5525,15 +5472,13 @@
     "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre", set
     <var title="">node</var> to 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>.
 
-    <p class=XXX>This doesn't really make sense if there's an intervening table
-    or something.
-
     <li>Let <var title="">current type</var> be the empty string.
 
     <li>If <var title="">node</var> is an <a href=#editable>editable</a> <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> "address", "div", "h1", "h2", "h3", "h4",
-    "h5", "h6", "p", or "pre", set <var title="">current type</var> to <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-element-local-name title=concept-element-local-name>local name</a>.
+    "h5", "h6", "p", or "pre", 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>, set <var title="">current type</var> to
+    <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-element-local-name title=concept-element-local-name>local name</a>.
 
     <li>If <var title="">type</var> is null, set <var title="">type</var> to <var title="">current
     type</var>.
@@ -5573,24 +5518,21 @@
   the same editing host</a> as <var title="">node</var>, and <var title="">node</var> is not
   an <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> "address", "div", "h1", "h2",
   "h3", "h4", "h5", "h6", "p", or "pre", set <var title="">node</var> to 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>.
-  <!--
-  Opera 11.11 doesn't require it be editable, so it will return "DIV" instead of
-  "" for <div contenteditable>foo</div>.
-
-  Chrome 14 dev will report "div" for <div><ol><li>foo</ol></div> or such.
-  Opera 11.11 reports "".  IE and Firefox didn't cooperate with testing.  Opera
-  makes somewhat more sense, but formatBlock is actually broken in that case
-  right now anyway for any wrapper other than div, so I'll punt on a solution.
-  -->
-  <p class=XXX>This doesn't really make sense if there's an intervening table
-  or something.
+  <!-- Opera 11.11 doesn't require it be editable, so it will return "DIV"
+  instead of "" for <div contenteditable>foo</div>.  -->
 
   <li>If <var title="">node</var> is an <a href=#editable>editable</a> <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> "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p",
-  or "pre", return its <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>, <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#converted-to-lowercase>converted
-  to lowercase</a>.
-  <!-- Actually, we don't really need to specify "editable", since it has to be
-  editable. -->
+  or "pre", 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>, return <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-element-local-name title=concept-element-local-name>local name</a>, <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#converted-to-lowercase>converted to lowercase</a>.
+  <!--
+  Chrome 14 dev will report "div" for <div><ol><li>foo</ol></div> or such.
+  Opera 11.11 reports "".  IE and Firefox didn't cooperate with testing.  Opera
+  makes more sense, and matches the fact that formatBlock now doesn't recognize
+  such a div as a formatBlock candidate, so Opera it is.
+
+  We don't really need to specify "editable", since it has to be editable.
+  -->
 
   <li>Return the empty string.
 </ol>
--- a/implementation.js	Wed Jun 29 14:50:14 2011 -0600
+++ b/implementation.js	Wed Jun 29 15:56:26 2011 -0600
@@ -81,6 +81,15 @@
 	return Boolean(node1.compareDocumentPosition(node2) & Node.DOCUMENT_POSITION_PRECEDING);
 }
 
+function getAncestors(node) {
+	var ancestors = [];
+	while (node.parentNode) {
+		ancestors.unshift(node.parentNode);
+		node = node.parentNode;
+	}
+	return ancestors;
+}
+
 function getDescendants(node) {
 	var descendants = [];
 	for (var i = 0; i < node.childNodes.length; i++) {
@@ -3494,6 +3503,11 @@
 //@{
 
 function fixDisallowedAncestors(node) {
+	// "If node is not editable, abort these steps."
+	if (!isEditable(node)) {
+		return;
+	}
+
 	// "If node is not an allowed child of any of its ancestors in the same
 	// editing host:"
 	var hasAllowedAncestor = false;
@@ -3535,59 +3549,6 @@
 	}
 }
 
-function fixProhibitedParagraphDescendants(node) {
-	// "If node has no children, return the one-node list consisting of node."
-	if (!node.hasChildNodes()) {
-		return [node];
-	}
-
-	// "Let children be the children of node."
-	var children = [].slice.call(node.childNodes);
-
-	// "Fix prohibited paragraph descendants of each member of children."
-	for (var i = 0; i < children.length; i++) {
-		fixProhibitedParagraphDescendants(children[i]);
-	}
-
-	// "Let children be the children of node."
-	children = [].slice.call(node.childNodes);
-
-	// "For each child in children, if child is a prohibited paragraph child,
-	// split the parent of the one-node list consisting of child."
-	for (var i = 0; i < children.length; i++) {
-		if (isProhibitedParagraphChild(children[i])) {
-			splitParent([children[i]]);
-		}
-	}
-
-	// "If node's parent is null, let node equal the last member of children."
-	if (!node.parentNode) {
-		node = children[children.length - 1];
-	}
-
-	// "Let node list be a list of nodes, initially empty."
-	var nodeList = [];
-
-	// "Repeat these steps:"
-	while (true) {
-		// "Prepend node to node list."
-		nodeList.unshift(node);
-
-		// "If node is children's first member, or children's first member's
-		// parent, break from this loop."
-		if (node == children[0]
-		|| node == children[0].parentNode) {
-			break;
-		}
-
-		// "Set node to its previousSibling."
-		node = node.previousSibling;
-	}
-
-	// "Return node list."
-	return nodeList;
-}
-
 function indentNodes(nodeList) {
 	// "If node list is empty, do nothing and abort these steps."
 	if (!nodeList.length) {
@@ -5476,59 +5437,37 @@
 		// "Block-extend the active range, and let new range be the result."
 		var newRange = blockExtend(getActiveRange());
 
-		// "Let original node list be an empty list of nodes."
-		var originalNodeList = [];
-
-		// "For each node node contained in new range, append node to original
-		// node list if it is editable, the last member of original node list
-		// (if any) is not an ancestor of node, and node is either a non-list
-		// single-line container or an allowed child of "p"."
-		originalNodeList = getContainedNodes(newRange, function(node) {
+		// "Let node list be an empty list of nodes."
+		//
+		// "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."
+		var nodeList = getContainedNodes(newRange, function(node) {
 			return isEditable(node)
 				&& (isNonListSingleLineContainer(node)
-				|| isAllowedChild(node, "p"));
+				|| isAllowedChild(node, "p"))
+				&& !getDescendants(node).some(isProhibitedParagraphChild);
 		});
 
-		// "For each node in original node list, while either node is a
-		// descendant of an editable HTML element in the same editing host with
-		// local name "address", "h1", "h2", "h3", "h4", "h5", "h6", "p", or
-		// "pre"; or node's parent is not null, and "p" is not an allowed child
-		// of node's parent: split the parent of the one-node list consisting
-		// of node."
-		for (var i = 0; i < originalNodeList.length; i++) {
-			var node = originalNodeList[i];
-
-			while (true) {
-				if (node.parentNode
-				&& !isAllowedChild("p", node.parentNode)) {
-					splitParent([node]);
-					continue;
-				}
-
-				var ancestor = node.parentNode;
-				while (ancestor
-				&& !isHtmlElement(ancestor, ["ADDRESS", "H1", "H2", "H3", "H4", "H5", "H6", "P", "PRE"])) {
-					ancestor = ancestor.parentNode;
-				}
-				if (ancestor
-				&& isEditable(ancestor)
-				&& inSameEditingHost(node, ancestor)) {
-					splitParent([node]);
-				} else {
-					break;
-				}
+		// "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."
+		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"])
+					&& !getDescendants(ancestor).some(isProhibitedParagraphChild);
+			})) {
+				splitParent([node]);
 			}
 		}
 
-		// "Let node list be a list of nodes, initially empty."
-		var nodeList = [];
-
-		// "For each node in original node list, fix prohibited paragraph
-		// descendants of node, and append the resulting nodes to node list."
-		for (var i = 0; i < originalNodeList.length; i++) {
-			nodeList = nodeList.concat(fixProhibitedParagraphDescendants(originalNodeList[i]));
-		}
-
 		// "If value is "div" or "p", then while node list is not empty:"
 		if (value == "div" || value == "p") {
 			while (nodeList.length) {
@@ -5564,10 +5503,11 @@
 
 				// "Wrap sublist, with sibling criteria matching nothing and
 				// new parent instructions returning the result of running
-				// createElement(value) on the context object."
-				wrap(sublist,
+				// createElement(value) on the context object. Then fix
+				// disallowed ancestors of the result."
+				fixDisallowedAncestors(wrap(sublist,
 					function() { return false },
-					function() { return document.createElement(value) });
+					function() { return document.createElement(value) }));
 			}
 
 		// "Otherwise, while node list is not empty:"
@@ -5615,10 +5555,11 @@
 				// "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,
+				// 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) });
+					function() { return document.createElement(value) }));
 			}
 		}
 	}, indeterm: function() {
@@ -5659,10 +5600,12 @@
 			var currentType = "";
 
 			// "If node is an editable HTML element with local name "address",
-			// "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre", set
+			// "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre", and
+			// node is not the ancestor of a prohibited paragraph child, set
 			// current type to node's local name."
 			if (isEditable(node)
-			&& isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"])) {
+			&& isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"])
+			&& !getDescendants(node).some(isProhibitedParagraphChild)) {
 				currentType = node.tagName;
 			}
 
@@ -5706,10 +5649,12 @@
 		}
 
 		// "If node is an editable HTML element with local name "address",
-		// "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre", return its
+		// "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre", and node
+		// is not the ancestor of a prohibited paragraph child, return node's
 		// local name, converted to lowercase."
 		if (isEditable(node)
-		&& isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"])) {
+		&& isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"])
+		&& !getDescendants(node).some(isProhibitedParagraphChild)) {
 			return node.tagName.toLowerCase();
 		}
 
--- a/source.html	Wed Jun 29 14:50:14 2011 -0600
+++ b/source.html	Wed Jun 29 15:56:26 2011 -0600
@@ -3103,12 +3103,14 @@
 
 <h3>Assorted block formatting command algorithms</h3>
 <!-- @{ -->
-<p>To <dfn>fix disallowed ancestors</dfn> of a [[node]] <var>node</var>:
+<p>To <dfn>fix disallowed ancestors</dfn> of <var>node</var>:
 
 <p class=XXX>Do we want to get rid of attributes that are no longer allowed
 here?
 
 <ol>
+  <li>If <var>node</var> is not <span>editable</span>, abort these steps.
+
   <li>If <var>node</var> is not an <span>allowed child</span> of any of its
   [[ancestors]] <span>in the same editing host</span>:
 
@@ -3136,58 +3138,6 @@
   of <var>node</var>.
 </ol>
 
-<p>To <dfn>fix prohibited paragraph descendants</dfn> of a [[node]]
-<var>node</var>:
-
-<p class=note>This will fix any case where a descendant of node is a prohibited
-paragraph child.  The function returns a list consisting of <var>node</var> and
-any new siblings this algorithm might give it, if some of its children are
-split out of it.
-
-<p class=XXX>Possibly can be converted to just fix disallowed ancestors of all
-descendant nodes.  The only reason this is nontrivial is because the caller
-needs the list of returned nodes.  (It's only used by formatBlock, but it
-currently has to be a separate function because it's recursive.)
-
-<ol>
-  <li>If <var>node</var> has no [[children]], return the one-[[node]] list
-  consisting of <var>node</var>.
-
-  <li>Let <var>children</var> be the [[children]] of <var>node</var>.
-
-  <li><span>Fix prohibited paragraph descendants</span> of each member of
-  <var>children</var>.
-
-  <!-- When we split the parent of a node, three things can happen.  Either the
-  node gets stuck before its parent, or after its parent, or the parent gets
-  copied and the copy gets stuck before it.  So the nodes we want to return are
-  consecutive siblings; the first is either the original first child of node or
-  its parent, and the last is either the last child of node or node itself. -->
-  <li>Let <var>children</var> be the [[children]] of <var>node</var>.
-
-  <li>For each <var>child</var> in <var>children</var>, if <var>child</var> is
-  a <span>prohibited paragraph child</span>, <span>split the parent</span> of
-  the one-[[node]] list consisting of <var>child</var>.
-
-  <li>If <var>node</var>'s [[parent]] is null, let <var>node</var> equal the
-  last member of <var>children</var>.
-
-  <li>Let <var>node list</var> be a list of [[nodes]], initially empty.
-
-  <li>Repeat these steps:
-
-  <ol>
-    <li>Prepend <var>node</var> to <var>node list</var>.
-
-    <li>If <var>node</var> is <var>children</var>'s first member, or
-    <var>children</var>'s first member's [[parent]], break from this loop.
-
-    <li>Set <var>node</var> to its [[previoussibling]].
-  </ol>
-
-  <li>Return <var>node list</var>.
-</ol>
-
 <p>To <dfn>indent</dfn> a list <var>node list</var> of consecutive [[sibling]]
 [[nodes]]:
 <!--
@@ -5363,44 +5313,34 @@
   <li><span>Block-extend</span> the <span>active range</span>, and let <var>new
   range</var> be the result.
 
-  <li>Let <var>original node list</var> be an empty list of [[nodes]].
+  <li>Let <var>node list</var> be an empty list of [[nodes]].
 
   <li>For each [[node]] <var>node</var> [[contained]] in <var>new range</var>,
-  append <var>node</var> to <var>original node list</var> if it is
+  append <var>node</var> to <var>node list</var> if it is
   <span>editable</span>, the last member of <var>original node list</var> (if
-  any) is not an [[ancestor]] of <var>node</var>, and <var>node</var> is either
-  a <span>non-list single-line container</span> or an <span>allowed
-  child</span> of "p".
-
-  <li>For each <var>node</var> in <var>original node list</var>, while either
-  <var>node</var> is a [[descendant]] of an <span>editable</span> <span>HTML
-  element</span> <span>in the same editing host</span> with [[localname]]
-  "address", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre"; or
-  <var>node</var>'s [[parent]] is not null, and "p" is not an <span>allowed
-  child</span> of <var>node</var>'s [[parent]]: <span>split the parent</span>
-  of the one-[[node]] list consisting of <var>node</var>.
-
-  <div class=XXX>
-  <p>This is needed so we don't get things like p nested inside address, and
-  instead convert part of the multi-line address into a p.  But div can contain
-  any of these things, so we don't break that apart ever, which is bad.  It
-  means if you have
-
-  <xmp><div>foo<br>bar</div></xmp>
-
-  <p>then formatBlocking "foo" then "bar" as p has different results from doing
-  both at once.  Maybe we should split divs as well, but only if they're the
-  parent of the node we're dealing with?  Or just split divs always, and hope
-  the "in the same editing host" thing handles it?
-  </div>
-
-  <p class=XXX>We probably don't want to break out of tables and such here.
-
-  <li>Let <var>node list</var> be a list of [[nodes]], initially empty.
-
-  <li>For each <var>node</var> in <var>original node list</var>, <span>fix
-  prohibited paragraph descendants</span> of <var>node</var>, and append the
-  resulting [[nodes]] to <var>node list</var>.
+  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>.
+
+  <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>.
+  <!--
+  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
+  <h1><p>[foo]</p><br>bar</h1> or such.  It tries to heuristically distinguish
+  between divs used as line-breakers and divs used as actual wrappers by
+  checking if they have prohibited paragraph children as descendants.  It works
+  for address too, in case there are paragraphs nested inside.  Thus
+  <address>[foo]<br>bar</address> becomes <p>[foo]</p><address>bar</address>,
+  but <address>[foo]<p>bar</p></address> becomes
+  <address><p>[foo]</p><p>bar</p></address>.  Likewise, we don't break things
+  out of lists or tables or such if they happen to be nested in a <div>.
+  -->
 
   <!--
   We have two different behaviors, one for div and p and one for everything
@@ -5461,7 +5401,13 @@
     <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]].
+    [[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:
@@ -5500,7 +5446,8 @@
     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]].
+    [[createelement|<var>value</var>]] on the [[contextobject]].  Then
+    <span>fix disallowed ancestors</span> of the result.
   </ol>
 </ol>
 
@@ -5531,15 +5478,13 @@
     "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre", set
     <var>node</var> to its [[parent]].
 
-    <p class=XXX>This doesn't really make sense if there's an intervening table
-    or something.
-
     <li>Let <var>current type</var> be the empty string.
 
     <li>If <var>node</var> is an <span>editable</span> <span>HTML
     element</span> with [[localname]] "address", "div", "h1", "h2", "h3", "h4",
-    "h5", "h6", "p", or "pre", set <var>current type</var> to <var>node</var>'s
-    [[localname]].
+    "h5", "h6", "p", or "pre", and <var>node</var> is not the [[ancestor]] of a
+    <span>prohibited paragraph child</span>, set <var>current type</var> to
+    <var>node</var>'s [[localname]].
 
     <li>If <var>type</var> is null, set <var>type</var> to <var>current
     type</var>.
@@ -5579,24 +5524,22 @@
   the same editing host</span> as <var>node</var>, and <var>node</var> is not
   an <span>HTML element</span> with [[localname]] "address", "div", "h1", "h2",
   "h3", "h4", "h5", "h6", "p", or "pre", set <var>node</var> to its [[parent]].
-  <!--
-  Opera 11.11 doesn't require it be editable, so it will return "DIV" instead of
-  "" for <div contenteditable>foo</div>.
-
-  Chrome 14 dev will report "div" for <div><ol><li>foo</ol></div> or such.
-  Opera 11.11 reports "".  IE and Firefox didn't cooperate with testing.  Opera
-  makes somewhat more sense, but formatBlock is actually broken in that case
-  right now anyway for any wrapper other than div, so I'll punt on a solution.
-  -->
-  <p class=XXX>This doesn't really make sense if there's an intervening table
-  or something.
+  <!-- Opera 11.11 doesn't require it be editable, so it will return "DIV"
+  instead of "" for <div contenteditable>foo</div>.  -->
 
   <li>If <var>node</var> is an <span>editable</span> <span>HTML element</span>
   with [[localname]] "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p",
-  or "pre", return its [[localname]], <span data-anolis-spec=domcore>converted
-  to lowercase</span>.
-  <!-- Actually, we don't really need to specify "editable", since it has to be
-  editable. -->
+  or "pre", and <var>node</var> is not the [[ancestor]] of a <span>prohibited
+  paragraph child</span>, return <var>node</var>'s [[localname]], <span
+  data-anolis-spec=domcore>converted to lowercase</span>.
+  <!--
+  Chrome 14 dev will report "div" for <div><ol><li>foo</ol></div> or such.
+  Opera 11.11 reports "".  IE and Firefox didn't cooperate with testing.  Opera
+  makes more sense, and matches the fact that formatBlock now doesn't recognize
+  such a div as a formatBlock candidate, so Opera it is.
+
+  We don't really need to specify "editable", since it has to be editable.
+  -->
 
   <li>Return the empty string.
 </ol>