Move around tons of stuff for clarity
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Tue, 14 Jun 2011 14:37:27 -0600
changeset 268 5cd6c5cdc9eb
parent 267 3269074b8e4f
child 269 bcaca2e22898
Move around tons of stuff for clarity

This involves only mild changes to source.html, since I already
refactored that a while ago. It's mostly moving around stuff in
implementation.js. I also started using vim folding. More such changes
to come. The files have grown huge and unmanageable, so reorganization
is necessary.
editcommands.html
implementation.js
source.html
--- a/editcommands.html	Tue Jun 14 13:46:20 2011 -0600
+++ b/editcommands.html	Tue Jun 14 14:37:27 2011 -0600
@@ -87,20 +87,19 @@
    <li><a href=#the-backcolor-command><span class=secno>6.8 </span>The <code title="">backColor</code> command</a></li>
    <li><a href=#the-bold-command><span class=secno>6.9 </span>The <code title="">bold</code> command</a></li>
    <li><a href=#the-createlink-command><span class=secno>6.10 </span>The <code title="">createLink</code> command</a></li>
-   <li><a href=#the-delete-command><span class=secno>6.11 </span>The <code title="">delete</code> command</a></li>
-   <li><a href=#the-fontname-command><span class=secno>6.12 </span>The <code title="">fontName</code> command</a></li>
-   <li><a href=#the-fontsize-command><span class=secno>6.13 </span>The <code title="">fontSize</code> command</a></li>
-   <li><a href=#the-forecolor-command><span class=secno>6.14 </span>The <code title="">foreColor</code> command</a></li>
-   <li><a href=#the-hilitecolor-command><span class=secno>6.15 </span>The <code title="">hiliteColor</code> command</a></li>
-   <li><a href=#the-inserthtml-command><span class=secno>6.16 </span>The <code title="">insertHTML</code> command</a></li>
-   <li><a href=#the-insertimage-command><span class=secno>6.17 </span>The <code title="">insertImage</code> command</a></li>
-   <li><a href=#the-italic-command><span class=secno>6.18 </span>The <code title="">italic</code> command</a></li>
-   <li><a href=#the-removeformat-command><span class=secno>6.19 </span>The <code title="">removeFormat</code> command</a></li>
-   <li><a href=#the-strikethrough-command><span class=secno>6.20 </span>The <code title="">strikethrough</code> command</a></li>
-   <li><a href=#the-subscript-command><span class=secno>6.21 </span>The <code title="">subscript</code> command</a></li>
-   <li><a href=#the-superscript-command><span class=secno>6.22 </span>The <code title="">superscript</code> command</a></li>
-   <li><a href=#the-underline-command><span class=secno>6.23 </span>The <code title="">underline</code> command</a></li>
-   <li><a href=#the-unlink-command><span class=secno>6.24 </span>The <code title="">unlink</code> command</a></ol></li>
+   <li><a href=#the-fontname-command><span class=secno>6.11 </span>The <code title="">fontName</code> command</a></li>
+   <li><a href=#the-fontsize-command><span class=secno>6.12 </span>The <code title="">fontSize</code> command</a></li>
+   <li><a href=#the-forecolor-command><span class=secno>6.13 </span>The <code title="">foreColor</code> command</a></li>
+   <li><a href=#the-hilitecolor-command><span class=secno>6.14 </span>The <code title="">hiliteColor</code> command</a></li>
+   <li><a href=#the-inserthtml-command><span class=secno>6.15 </span>The <code title="">insertHTML</code> command</a></li>
+   <li><a href=#the-insertimage-command><span class=secno>6.16 </span>The <code title="">insertImage</code> command</a></li>
+   <li><a href=#the-italic-command><span class=secno>6.17 </span>The <code title="">italic</code> command</a></li>
+   <li><a href=#the-removeformat-command><span class=secno>6.18 </span>The <code title="">removeFormat</code> command</a></li>
+   <li><a href=#the-strikethrough-command><span class=secno>6.19 </span>The <code title="">strikethrough</code> command</a></li>
+   <li><a href=#the-subscript-command><span class=secno>6.20 </span>The <code title="">subscript</code> command</a></li>
+   <li><a href=#the-superscript-command><span class=secno>6.21 </span>The <code title="">superscript</code> command</a></li>
+   <li><a href=#the-underline-command><span class=secno>6.22 </span>The <code title="">underline</code> command</a></li>
+   <li><a href=#the-unlink-command><span class=secno>6.23 </span>The <code title="">unlink</code> command</a></ol></li>
  <li><a href=#block-formatting-commands><span class=secno>7 </span>Block formatting commands</a>
   <ol>
    <li><a href=#block-formatting-command-definitions><span class=secno>7.1 </span>Block formatting command definitions</a></li>
@@ -110,17 +109,18 @@
    <li><a href=#outdenting-a-node><span class=secno>7.5 </span>Outdenting a node</a></li>
    <li><a href=#toggling-lists><span class=secno>7.6 </span>Toggling lists</a></li>
    <li><a href=#justifying-the-selection><span class=secno>7.7 </span>Justifying the selection</a></li>
-   <li><a href=#the-formatblock-command><span class=secno>7.8 </span>The <code title="">formatBlock</code> command</a></li>
-   <li><a href=#the-indent-command><span class=secno>7.9 </span>The <code title="">indent</code> command</a></li>
-   <li><a href=#the-inserthorizontalrule-command><span class=secno>7.10 </span>The <code title="">insertHorizontalRule</code> command</a></li>
-   <li><a href=#the-insertorderedlist-command><span class=secno>7.11 </span>The <code title="">insertOrderedList</code> command</a></li>
-   <li><a href=#the-insertparagraph-command><span class=secno>7.12 </span>The <code title="">insertParagraph</code> command</a></li>
-   <li><a href=#the-insertunorderedlist-command><span class=secno>7.13 </span>The <code title="">insertUnorderedList</code> command</a></li>
-   <li><a href=#the-justifycenter-command><span class=secno>7.14 </span>The <code title="">justifyCenter</code> command</a></li>
-   <li><a href=#the-justifyfull-command><span class=secno>7.15 </span>The <code title="">justifyFull</code> command</a></li>
-   <li><a href=#the-justifyleft-command><span class=secno>7.16 </span>The <code title="">justifyLeft</code> command</a></li>
-   <li><a href=#the-justifyright-command><span class=secno>7.17 </span>The <code title="">justifyRight</code> command</a></li>
-   <li><a href=#the-outdent-command><span class=secno>7.18 </span>The <code title="">outdent</code> command</a></ol></li>
+   <li><a href=#the-delete-command><span class=secno>7.8 </span>The <code title="">delete</code> command</a></li>
+   <li><a href=#the-formatblock-command><span class=secno>7.9 </span>The <code title="">formatBlock</code> command</a></li>
+   <li><a href=#the-indent-command><span class=secno>7.10 </span>The <code title="">indent</code> command</a></li>
+   <li><a href=#the-inserthorizontalrule-command><span class=secno>7.11 </span>The <code title="">insertHorizontalRule</code> command</a></li>
+   <li><a href=#the-insertorderedlist-command><span class=secno>7.12 </span>The <code title="">insertOrderedList</code> command</a></li>
+   <li><a href=#the-insertparagraph-command><span class=secno>7.13 </span>The <code title="">insertParagraph</code> command</a></li>
+   <li><a href=#the-insertunorderedlist-command><span class=secno>7.14 </span>The <code title="">insertUnorderedList</code> command</a></li>
+   <li><a href=#the-justifycenter-command><span class=secno>7.15 </span>The <code title="">justifyCenter</code> command</a></li>
+   <li><a href=#the-justifyfull-command><span class=secno>7.16 </span>The <code title="">justifyFull</code> command</a></li>
+   <li><a href=#the-justifyleft-command><span class=secno>7.17 </span>The <code title="">justifyLeft</code> command</a></li>
+   <li><a href=#the-justifyright-command><span class=secno>7.18 </span>The <code title="">justifyRight</code> command</a></li>
+   <li><a href=#the-outdent-command><span class=secno>7.19 </span>The <code title="">outdent</code> command</a></ol></li>
  <li><a href=#miscellaneous-commands><span class=secno>8 </span>Miscellaneous commands</a>
   <ol>
    <li><a href=#the-selectall-command><span class=secno>8.1 </span>The <code title="">selectAll</code> command</a></li>
@@ -411,6 +411,79 @@
 things.  If it is, it needs some adjustment, like to handle collapsed
 whitespace nodes and collapsed br's.
 
+<!-- I don't remember why I wrote this.  Keeping it around just in case it
+turns out to be useful.
+
+<p><var title>node</var> is an <dfn>extraneous line break</dfn> if the following
+algorithm returns true:
+
+<p class=XXX>This is positively horrible.  It attempts to move complicated CSS
+logic into DOM methods, and does so badly.  Can we somehow make this less evil,
+preferably much less evil?
+
+<ol>
+  <li>If <var title>node</var> is not a <code data-anolis-spec=html title="the br element">br</code>, return false.
+
+  <li>Let <var title>ancestor block</var> be <var title>node</var>.
+
+  <li>While <var title>ancestor block</var> is an <span>inline node</span>, set
+  <var title>ancestor block</var> to its <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
+
+  <li>Let <var title>previous box</var> be <var title>node</var>.
+
+  <li>While <var title>previous box</var> is equal to or an <span data-anolis-spec=domcore title=concept-tree-ancestor>ancestor</span> of
+  <var title>node</var>, set <var title>previous box</var> to the <span data-anolis-spec=domcore title=concept-node>node</span> immediately
+  before it in <span data-anolis-spec=domcore>tree order</span>, or null if there is no such <span data-anolis-spec=domcore title=concept-node>node</span>.
+
+  <li>Let <var title>next box</var> be the <span data-anolis-spec=domcore title=concept-node>node</span> immediately after <var title>node</var>
+  in <span data-anolis-spec=domcore>tree order</span>, or null if there is such <span data-anolis-spec=domcore title=concept-node>node</span>.
+
+  <li>If <var title>previous box</var> is null, or is not a <span data-anolis-spec=domcore title=concept-tree-descendant>descendant</span> of
+  <var title>ancestor block</var>, or is not an <span>inline node</span>, or is a
+  <code data-anolis-spec=html title="the br element">br</code>, return false.
+  <!- -
+  This means br either is the first thing in its block container that generates
+  an inline box, or it's the first thing after another block container, or the
+  first thing after a br.  In any case, the line break will be visible.
+
+  Otherwise, it will be invisible as long as it immediately precedes a block
+  box boundary.
+  - ->
+
+  <li>If <var title>ancestor block</var> is not null and <var title>node</var> is the last
+  <span data-anolis-spec=domcore title=concept-tree-descendant>descendant</span> of <var title>ancestor block</var>, return true.
+  <!- - Precedes the end of ancestor block's box. - ->
+
+  <li>If <var title>next box</var> is not null and not an <span>inline node</span>,
+  return true.
+  <!- - Precedes the start of next box's box. - ->
+
+  <li>Return false.
+</ol>
+-->
+
+<p>Each <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> has a boolean <dfn id=css-styling-flag>CSS styling flag</dfn> associated
+with it, which must initially be false.  (<a href=#the-stylewithcss-command>The <code title="">styleWithCSS</code> command</a> can be used to modify or query it, by
+means of the <code><a href=#execcommand()>execCommand()</a></code> and <code><a href=#querycommandstate()>queryCommandState()</a></code>
+methods.)
+
+<p>When the user agent is instructed to run a particular method, it must follow
+the steps defined for that method in the appropriate specification, not act as
+though the method had actually been called from JavaScript.  In particular,
+if the author has overridden the method with a custom method, the standard
+method must be run rather than the custom one.
+
+<p>When a list or set 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> is assigned to a variable without specifying
+the order, they must be initially 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>, if they share a root.
+(If they don't share a root, the order will be specified.)  When the user agent
+is instructed to run particular steps for each member of a list, it must do so
+sequentially in the list's order.
+
+
+<h2 id=common-algorithms><span class=secno>5 </span>Common algorithms</h2>
+
+<h3 id=assorted-common-algorithms><span class=secno>5.1 </span>Assorted common algorithms</h3>
+
 <p>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> or string <var title="">child</var> is an <dfn id=allowed-child>allowed child</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> or string <var title="">parent</var> if the following algorithm returns true:
 
@@ -534,78 +607,34 @@
   <li>Return true.
 </ol>
 
-<!-- I don't remember why I wrote this.  Keeping it around just in case it
-turns out to be useful.
-
-<p><var title>node</var> is an <dfn>extraneous line break</dfn> if the following
-algorithm returns true:
-
-<p class=XXX>This is positively horrible.  It attempts to move complicated CSS
-logic into DOM methods, and does so badly.  Can we somehow make this less evil,
-preferably much less evil?
+<p>To move 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> to a new location, <dfn id=preserving-ranges>preserving ranges</dfn>, remove
+the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> from its original <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> (if any), then insert it in the new
+location.  In doing so, however, ignore the regular <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#range-mutation-rules>range mutation rules</a>, and
+instead follow these rules:
 
 <ol>
-  <li>If <var title>node</var> is not a <code data-anolis-spec=html title="the br element">br</code>, return false.
-
-  <li>Let <var title>ancestor block</var> be <var title>node</var>.
-
-  <li>While <var title>ancestor block</var> is an <span>inline node</span>, set
-  <var title>ancestor block</var> to its <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
-
-  <li>Let <var title>previous box</var> be <var title>node</var>.
-
-  <li>While <var title>previous box</var> is equal to or an <span data-anolis-spec=domcore title=concept-tree-ancestor>ancestor</span> of
-  <var title>node</var>, set <var title>previous box</var> to the <span data-anolis-spec=domcore title=concept-node>node</span> immediately
-  before it in <span data-anolis-spec=domcore>tree order</span>, or null if there is no such <span data-anolis-spec=domcore title=concept-node>node</span>.
-
-  <li>Let <var title>next box</var> be the <span data-anolis-spec=domcore title=concept-node>node</span> immediately after <var title>node</var>
-  in <span data-anolis-spec=domcore>tree order</span>, or null if there is such <span data-anolis-spec=domcore title=concept-node>node</span>.
-
-  <li>If <var title>previous box</var> is null, or is not a <span data-anolis-spec=domcore title=concept-tree-descendant>descendant</span> of
-  <var title>ancestor block</var>, or is not an <span>inline node</span>, or is a
-  <code data-anolis-spec=html title="the br element">br</code>, return false.
-  <!- -
-  This means br either is the first thing in its block container that generates
-  an inline box, or it's the first thing after another block container, or the
-  first thing after a br.  In any case, the line break will be visible.
-
-  Otherwise, it will be invisible as long as it immediately precedes a block
-  box boundary.
-  - ->
-
-  <li>If <var title>ancestor block</var> is not null and <var title>node</var> is the last
-  <span data-anolis-spec=domcore title=concept-tree-descendant>descendant</span> of <var title>ancestor block</var>, return true.
-  <!- - Precedes the end of ancestor block's box. - ->
-
-  <li>If <var title>next box</var> is not null and not an <span>inline node</span>,
-  return true.
-  <!- - Precedes the start of next box's box. - ->
-
-  <li>Return false.
+  <li>Let <var title="">node</var> be the moved <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="">old parent</var> and
+  <var title="">old index</var> be the old <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> (which may be null) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>,
+  and <var title="">new parent</var> and <var title="">new index</var> be the new <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> and
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>.
+
+  <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is the same as or 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
+  <var title="">node</var>, leave it unchanged, so it moves to the new location.  <!--
+  This is actually implicit, but I state it anyway for completeness. -->
+
+  <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">new parent</var> and its
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is greater than <var title="">new index</var>, add one to its
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
+
+  <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">old parent</var> and its
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is <var title="">old index</var> or <var title="">old index</var> + 1, set its
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> to <var title="">new parent</var> and add <var title="">new index</var> &minus;
+  <var title="">old index</var> to its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
+
+  <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">old parent</var> and its
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is greater than <var title="">old index</var> + 1, subtract one from its
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
 </ol>
--->
-
-<p>Each <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> has a boolean <dfn id=css-styling-flag>CSS styling flag</dfn> associated
-with it, which must initially be false.  (<a href=#the-stylewithcss-command>The <code title="">styleWithCSS</code> command</a> can be used to modify or query it, by
-means of the <code><a href=#execcommand()>execCommand()</a></code> and <code><a href=#querycommandstate()>queryCommandState()</a></code>
-methods.)
-
-<p>When the user agent is instructed to run a particular method, it must follow
-the steps defined for that method in the appropriate specification, not act as
-though the method had actually been called from JavaScript.  In particular,
-if the author has overridden the method with a custom method, the standard
-method must be run rather than the custom one.
-
-<p>When a list or set 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> is assigned to a variable without specifying
-the order, they must be initially 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>, if they share a root.
-(If they don't share a root, the order will be specified.)  When the user agent
-is instructed to run particular steps for each member of a list, it must do so
-sequentially in the list's order.
-
-
-<h2 id=common-algorithms><span class=secno>5 </span>Common algorithms</h2>
-
-<h3 id=assorted-common-algorithms><span class=secno>5.1 </span>Assorted common algorithms</h3>
 
 <p>To <dfn id=set-the-tag-name>set the tag name</dfn> of an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> <var title="">element</var> to
 <var title="">new name</var>:
@@ -786,35 +815,6 @@
 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>.
 
-<p>To move 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> to a new location, <dfn id=preserving-ranges>preserving ranges</dfn>, remove
-the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> from its original <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> (if any), then insert it in the new
-location.  In doing so, however, ignore the regular <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#range-mutation-rules>range mutation rules</a>, and
-instead follow these rules:
-
-<ol>
-  <li>Let <var title="">node</var> be the moved <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="">old parent</var> and
-  <var title="">old index</var> be the old <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> (which may be null) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>,
-  and <var title="">new parent</var> and <var title="">new index</var> be the new <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> and
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>.
-
-  <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is the same as or 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
-  <var title="">node</var>, leave it unchanged, so it moves to the new location.  <!--
-  This is actually implicit, but I state it anyway for completeness. -->
-
-  <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">new parent</var> and its
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is greater than <var title="">new index</var>, add one to its
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
-
-  <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">old parent</var> and its
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is <var title="">old index</var> or <var title="">old index</var> + 1, set its
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> to <var title="">new parent</var> and add <var title="">new index</var> &minus;
-  <var title="">old index</var> to its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
-
-  <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">old parent</var> and its
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is greater than <var title="">old index</var> + 1, subtract one from its
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
-</ol>
-
 
 <h3 id=wrapping-a-list-of-nodes><span class=secno>5.2 </span>Wrapping a list of nodes</h3>
 
@@ -1410,6 +1410,9 @@
 display property computes to something other than "inline", "inline-block", or
 "inline-table"; or any <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> that is not <a href=#editable>editable</a>.
 
+<p class=XXX>Probably want to redefine unwrappable node in terms of allowed
+children or something.
+
 <p>A <dfn id=modifiable-element>modifiable element</dfn> is a <code class=external data-anolis-spec=html title="the b element"><a href=http://www.whatwg.org/html/#the-b-element>b</a></code>, <code class=external data-anolis-spec=html title="the em element"><a href=http://www.whatwg.org/html/#the-em-element>em</a></code>, <code class=external data-anolis-spec=html title="the i element"><a href=http://www.whatwg.org/html/#the-i-element>i</a></code>, <code class=external data-anolis-spec=html title="the s element"><a href=http://www.whatwg.org/html/#the-s-element>s</a></code>, <code class=external data-anolis-spec=html title="the span element"><a href=http://www.whatwg.org/html/#the-span-element>span</a></code>,
 <code class=external data-anolis-spec=html title="the strike element"><a href=http://www.whatwg.org/html/#the-strike-element>strike</a></code>, <code class=external data-anolis-spec=html title="the strong element"><a href=http://www.whatwg.org/html/#the-strong-element>strong</a></code>, <code class=external data-anolis-spec=html title="the sub and sup elements"><a href=http://www.whatwg.org/html/#the-sub-and-sup-elements>sub</a></code>, <code class=external data-anolis-spec=html title="the sub and sup elements"><a href=http://www.whatwg.org/html/#the-sub-and-sup-elements>sup</a></code>, or <code class=external data-anolis-spec=html title="the u element"><a href=http://www.whatwg.org/html/#the-u-element>u</a></code> element with no attributes
 except possibly <code class=external data-anolis-spec=html title="the style attribute"><a href=http://www.whatwg.org/html/#the-style-attribute>style</a></code>; or a <code class=external data-anolis-spec=html title=font><a href=http://www.whatwg.org/html/#font>font</a></code> element with no attributes except
@@ -2474,282 +2477,7 @@
 <!-- I'd have expected the value to be the URL, but guess not. -->
 
 
-<h3 id=the-delete-command><span class=secno>6.11 </span><dfn>The <code title="">delete</code> command</dfn></h3>
-<!-- Not really specifically "inline", but I didn't want to create a new
-section just for it. -->
-
-<p><a href=#action>Action</a>:
-
-<ol>
-  <li>If the <a href=#active-range>active range</a> is not <code class=external data-anolis-spec=domrange title=dom-Range-collapsed><a href=http://html5.org/specs/dom-range.html#dom-range-collapsed>collapsed</a></code>, <a href=#delete-the-contents>delete the contents</a>
-  of the <a href=#active-range>active range</a> and abort these steps.
-
-  <p class=XXX>Maybe we sometimes want to do the following even if it isn't
-  collapsed?  WebKit seems to do some normalization on the range before
-  deciding whether it's collapsed, and that sounds like a good idea.
-
-  <li>Let <var title="">node</var> and <var title="">offset</var> be the <a href=#active-range>active
-  range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
-
-  <!-- First go up as high as possible within the current block, then drill
-  down to the lowest possible level, in the hopes that we'll wind up at the end
-  of a text node, or maybe in a br or hr. -->
-  <li>Repeat the following steps:
-
-  <ol>
-    <!--
-    If there's an invisible node somewhere, Firefox 5.0a2 removes that node and
-    then stops, so each backspace removes one invisible node.  All others
-    remove the invisible node and then continue on looking for something
-    visible to remove.  The spec follows the latter behavior, since it makes
-    more sense to the user.  Of course, the definition of "invisible node" is
-    not necessarily anything like the spec's.
-    -->
-    <li>If <var title="">offset</var> is zero and <var title="">node</var>'s <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>
-    is an <a href=#editable>editable</a> <a href=#invisible-node>invisible node</a>, remove
-    <var title="">node</var>'s <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> from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
-
-    <li>Otherwise, if <var title="">node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
-    <var title="">offset</var> &minus; 1 and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is an <a href=#editable>editable</a>
-    <a href=#invisible-node>invisible node</a>, remove that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> from <var title="">node</var>,
-    then subtract one from <var title="">offset</var>.
-
-    <li>Otherwise, if <var title="">offset</var> is zero and <var title="">node</var> is not a
-    <a href=#prohibited-paragraph-child>prohibited paragraph child</a>, or if <var title="">node</var> is an
-    <a href=#invisible-node>invisible node</a>, set <var title="">offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of
-    <var title="">node</var>, then 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>.
-
-    <li>Otherwise, if <var title="">node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
-    <var title="">offset</var> &minus; 1 and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is not a <a href=#prohibited-paragraph-child>prohibited
-    paragraph child</a> or a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or an <code class=external data-anolis-spec=html title="the img element"><a href=http://www.whatwg.org/html/#the-img-element>img</a></code>, set <var title="">node</var> to
-    that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>, then set <var title="">offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of
-    <var title="">node</var>.
-
-    <li>Otherwise, break from this loop.
-  </ol>
-
-  <!--
-  At this point, node cannot be an invisible node.  There are three cases:
-
-  1) offset is zero and node is a prohibited paragraph child.  Then we'll
-  usually merge with the previous block if one exists.
-
-  2) offset is not zero, node is not a prohibited paragraph child, and node
-  does not have a child with index offset - 1.  The only way this is possible
-  is if node has a length greater than zero but no children, which implies it's
-  a text or comment or PI.  Comments and PIs are invisible nodes, so it must be
-  a text node.  We delete the previous character.
-
-  3) offset is not zero, and the child of node with index offset - 1 is a
-  prohibited paragraph child or a br or an img.  Then we'll usually merge the
-  offsetth child of node with the last descendant of the offset - 1st.
-  -->
-
-  <li>If <var title="">node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node and <var title="">offset</var> is not zero,
-  call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
-  Then <a href=#delete-the-contents>delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
-  (<var title="">node</var>, <var title="">offset</var> &minus; 1) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>
-  (<var title="">node</var>, <var title="">offset</var>) and abort these steps.
-
-  <!-- At the time of this writing, this should be impossible. -->
-  <li>If <var title="">node</var> is not a <a href=#prohibited-paragraph-child>prohibited paragraph child</a>,
-  abort these steps.
-
-  <li>If <var title="">node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">offset</var>
-  &minus; 1 and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or <code class=external data-anolis-spec=html title="the hr element"><a href=http://www.whatwg.org/html/#the-hr-element>hr</a></code> or <code class=external data-anolis-spec=html title="the img element"><a href=http://www.whatwg.org/html/#the-img-element>img</a></code>, call
-  <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
-  Then <a href=#delete-the-contents>delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
-  (<var title="">node</var>, <var title="">offset</var> &minus; 1) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>
-  (<var title="">node</var>, <var title="">offset</var>) and abort these steps.
-
-  <!--
-  If we're at the beginning of a list, we want to outdent the first list item.
-  This doesn't actually match anyone or anything.  Word 2007 and OpenOffice.org
-  3.2.1 Ubuntu just remove the list marker, which is weird and doesn't map well
-  to HTML.  Browsers tend to just merge with the preceding block, which isn't
-  expected.
-  -->
-  <li>If <var title="">node</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> 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> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code> and is 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 its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>:
-
-  <ol>
-    <li>Let <var title="">items</var> be a list of all <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>s that are
-    <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> of <var title="">node</var>.
-
-    <li><a href=#normalize-sublists>Normalize sublists</a> of each <var title="">item</var> in
-    <var title="">items</var>.
-
-    <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><a href=#fix-disallowed-ancestors>Fix disallowed ancestors</a> of <var title="">node</var>.
-
-    <li>Abort these steps.
-  </ol>
-
-  <!-- By this point, we're almost certainly going to merge something, and the
-  only question is what. -->
-  <li>Let <var title="">start node</var> equal <var title="">node</var> and let <var title="">start
-  offset</var> equal <var title="">offset</var>.
-
-  <li>While <var title="">start offset</var> is zero, set <var title="">start offset</var> to the
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <var title="">start node</var> and then set <var title="">start 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>The node at index start offset &minus; 1 might be an invisible
-  node.
-
-  <!--
-  At the beginning of an indented block, outdent it, similar to a list item.
-  Browsers don't do this, word processors do.
-  -->
-  <li>If <var title="">offset</var> is zero, and <var title="">node</var> has an
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container title="ancestor container">ancestor container</a> that is both a <a href=#potential-indentation-element>potential indentation
-  element</a> and 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 <var title="">start node</var>:
-
-  <p class=XXX>This copy-pastes from the outdent command action.  I'm also not
-  totally sure it's correct.
-
-  <ol>
-    <li><a href=#block-extend>Block-extend</a> the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> whose <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and
-    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> are both (<var title="">node</var>, 0), and let <var title="">new range</var> be
-    the result.
-
-    <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 <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="">current 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="">current node</var> to <var title="">node list</var> if the
-    last member of <var title="">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="">current node</var>, and <var title="">current node</var> is
-    <a href=#editable>editable</a> but has no <a href=#editable>editable</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>descendants</a>.
-
-    <li><a href=#outdent>Outdent</a> 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>.
-
-    <li>Abort these steps.
-  </ol>
-
-  <!--
-  This is to avoid stripping a line break from
-
-    foo<br><br><table><tr><td>[]bar</table>
-
-  and similarly for <hr>.  We should just do nothing here.
-  -->
-  <li>If 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>child</a> of <var title="">start node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
-  offset</var> is a <code class=external data-anolis-spec=html title="the table element"><a href=http://www.whatwg.org/html/#the-table-element>table</a></code>, abort these steps.
-
-  <!--
-  If you try backspacing into a table, select it.  This doesn't match any
-  browser; it matches the recommendation of the "behavior when typing in
-  contentEditable elements" document.  The idea is that then you can delete it
-  with a second backspace.
-  -->
-  <li>If <var title="">start node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
-  offset</var> &minus; 1, and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is a <code class=external data-anolis-spec=html title="the table element"><a href=http://www.whatwg.org/html/#the-table-element>table</a></code>:
-
-  <ol>
-    <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">start node</var>, <var title="">start offset</var>
-    &minus; 1)</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
-
-    <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-extend><a href=http://html5.org/specs/dom-range.html#dom-selection-extend>extend(<var title="">start node</var>, <var title="">start offset</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>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
-
-    <li>Abort these steps.
-  </ol>
-
-  <!--
-  Special case:
-
-    <p>foo</p><br><p>[]bar</p>
-    -> <p>foo</p><p>[]bar</p>
-
-  and likewise for <hr>.  But with <img> we merge like in other cases:
-
-    <p>foo</p><img><p>[]bar</p>
-    -> <p>foo</p><img>[]bar.
-
-  Browsers don't do this consistently.  Firefox 5.0a2 doesn't seem to do it at
-  all.
-  -->
-  <li>If <var title="">offset</var> is zero; and either 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>child</a> of <var title="">start
-  node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> minus one is an <code class=external data-anolis-spec=html title="the hr element"><a href=http://www.whatwg.org/html/#the-hr-element>hr</a></code>, or
-  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>child</a> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> whose <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> is either a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or not
-  an <a href=#inline-node>inline node</a>:
-
-  <ol>
-    <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the
-    <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
-
-    <li><a href=#delete-the-contents>Delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
-    (<var title="">start node</var>, <var title="">start offset</var> &minus; 1) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>
-    (<var title="">start node</var>, <var title="">start offset</var>).
-
-    <li>Abort these steps.
-  </ol>
-
-  <!--
-  If you try backspacing out of a list item, merge it with the previous item,
-  but add a line break.  Then you have to backspace again if you really want
-  them to be on the same line.  This matches Word 2007 and OpenOffice.org 3.2.1
-  Ubuntu, and also matches "behavior when typing in contentEditable elements",
-  but does not match any browser.
-
-  Note that this behavior is quite different from what happens if you actually
-  select the linebreak in between the two lines.  In that case, the blocks are
-  merged as normal.
-
-  Also note that hitting backspace twice will merge with the previous item.
-  This matches OO.org, but Word will outdent the item on subsequent backspaces.
-  Word's behavior doesn't fit well with the way lists work in HTML, and we
-  probably don't want it.
-  -->
-  <li>If 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>child</a> of <var title="">start node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
-  offset</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> 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> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>'s
-  <code class=external data-anolis-spec=domcore title=dom-Node-firstChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-firstchild>firstChild</a></code> is an <a href=#inline-node>inline node</a>, and <var title="">start offset</var> is
-  not zero:
-
-  <ol>
-    <li>Let <var title="">previous item</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>child</a> of <var title="">start node</var>
-    with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> minus one.
-
-    <!-- If the last child is already a br, we only need to append one extra
-    br.  Otherwise we need to append two, since the first will do nothing. -->
-    <li>If <var title="">previous item</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-lastChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-lastchild>lastChild</a></code> is an <a href=#inline-node>inline
-    node</a> other than 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
-    <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="">previous item</var>.
-
-    <li>If <var title="">previous item</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-lastChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-lastchild>lastChild</a></code> is an <a href=#inline-node>inline
-    node</a>, call <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a> and
-    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="">previous item</var>.
-  </ol>
-
-  <!--
-  When merging adjacent list items, make sure we only merge the items
-  themselves, not any block children.  We want <li><p>foo<li><p>bar to become
-  <li><p>foo<p>bar, not <li><p>foo<br>bar or <li><p>foobar.
-  -->
-  <li>If 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>child</a> of <var title="">start node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
-  offset</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> 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> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, and 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> is
-  also an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> 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> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, set <var title="">start 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-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> &minus; 1, then set
-  <var title="">start offset</var> to <var title="">start node</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a>, then set
-  <var title="">node</var> to <var title="">start node</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>, then set
-  <var title="">offset</var> to 0.
-
-  <!-- General block-merging case. -->
-  <li>Otherwise, while <var title="">start node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
-  <var title="">start offset</var> minus one, set <var title="">start node</var> to that
-  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>, then set <var title="">start offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of
-  <var title="">start node</var>.
-
-  <li><a href=#delete-the-contents>Delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
-  (<var title="">start node</var>, <var title="">start offset</var>) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>
-  (<var title="">node</var>, <var title="">offset</var>).
-</ol>
-
-
-<h3 id=the-fontname-command><span class=secno>6.12 </span><dfn>The <code title="">fontName</code> command</dfn></h3>
+<h3 id=the-fontname-command><span class=secno>6.11 </span><dfn>The <code title="">fontName</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>,
 then <a href=#set-the-value>set the value</a> of each returned <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> to <var title="">value</var>.
@@ -2798,7 +2526,7 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "font-family"
 
 
-<h3 id=the-fontsize-command><span class=secno>6.13 </span><dfn>The <code title="">fontSize</code> command</dfn></h3>
+<h3 id=the-fontsize-command><span class=secno>6.12 </span><dfn>The <code title="">fontSize</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -2905,7 +2633,7 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "font-size"
 
 
-<h3 id=the-forecolor-command><span class=secno>6.14 </span><dfn>The <code title="">foreColor</code> command</dfn></h3>
+<h3 id=the-forecolor-command><span class=secno>6.13 </span><dfn>The <code title="">foreColor</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -2990,7 +2718,7 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "color"
 
 
-<h3 id=the-hilitecolor-command><span class=secno>6.15 </span><dfn>The <code title="">hiliteColor</code> command</dfn></h3>
+<h3 id=the-hilitecolor-command><span class=secno>6.14 </span><dfn>The <code title="">hiliteColor</code> command</dfn></h3>
 <!-- IE 9 RC doesn't support this.  It uses backColor instead, but Gecko and
 Opera treat that differently, while all non-IE browsers treat hiliteColor the
 same, so I'm standardizing hiliteColor as the way to highlight text.
@@ -3029,7 +2757,7 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "background-color"
 
 
-<h3 id=the-inserthtml-command><span class=secno>6.16 </span><dfn>The <code title="">insertHTML</code> command</dfn></h3>
+<h3 id=the-inserthtml-command><span class=secno>6.15 </span><dfn>The <code title="">insertHTML</code> command</dfn></h3>
 <!-- Not supported by IE9. -->
 
 <p><a href=#action>Action</a>:
@@ -3059,7 +2787,7 @@
 </ol>
 
 
-<h3 id=the-insertimage-command><span class=secno>6.17 </span><dfn>The <code title="">insertImage</code> command</dfn></h3>
+<h3 id=the-insertimage-command><span class=secno>6.16 </span><dfn>The <code title="">insertImage</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -3095,7 +2823,7 @@
 </ol>
 
 
-<h3 id=the-italic-command><span class=secno>6.18 </span><dfn>The <code title="">italic</code> command</dfn></h3>
+<h3 id=the-italic-command><span class=secno>6.17 </span><dfn>The <code title="">italic</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>.
 If the <a href=#state>state</a> is then false, <a href=#set-the-value>set the value</a> of each
@@ -3111,7 +2839,7 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "font-style"
 
 
-<h3 id=the-removeformat-command><span class=secno>6.19 </span><dfn>The <code title="">removeFormat</code> command</dfn></h3>
+<h3 id=the-removeformat-command><span class=secno>6.18 </span><dfn>The <code title="">removeFormat</code> command</dfn></h3>
 <!--
 Tested in IE 9, Firefox 4.0, Chrome 12 dev, Opera 11.00.
 
@@ -3232,7 +2960,7 @@
 </ol>
 
 
-<h3 id=the-strikethrough-command><span class=secno>6.20 </span><dfn>The <code title="">strikethrough</code> command</dfn></h3>
+<h3 id=the-strikethrough-command><span class=secno>6.19 </span><dfn>The <code title="">strikethrough</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>.
 If the <a href=#state>state</a> is then false, <a href=#set-the-value>set the value</a> of each
@@ -3248,7 +2976,7 @@
 <p><a href=#value>Value</a>: Always the empty string.
 
 
-<h3 id=the-subscript-command><span class=secno>6.21 </span><dfn>The <code title="">subscript</code> command</dfn></h3>
+<h3 id=the-subscript-command><span class=secno>6.20 </span><dfn>The <code title="">subscript</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -3273,7 +3001,7 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "vertical-align"
 
 
-<h3 id=the-superscript-command><span class=secno>6.22 </span><dfn>The <code title="">superscript</code> command</dfn></h3>
+<h3 id=the-superscript-command><span class=secno>6.21 </span><dfn>The <code title="">superscript</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -3298,7 +3026,7 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "vertical-align"
 
 
-<h3 id=the-underline-command><span class=secno>6.23 </span><dfn>The <code title="">underline</code> command</dfn></h3>
+<h3 id=the-underline-command><span class=secno>6.22 </span><dfn>The <code title="">underline</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>.
 If the <a href=#state>state</a> is then false, <a href=#set-the-value>set the value</a> of each
@@ -3360,7 +3088,7 @@
 <p><a href=#value>Value</a>: Always the empty string.
 
 
-<h3 id=the-unlink-command><span class=secno>6.24 </span><dfn>The <code title="">unlink</code> command</dfn></h3>
+<h3 id=the-unlink-command><span class=secno>6.23 </span><dfn>The <code title="">unlink</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -3745,6 +3473,9 @@
 
 <h3 id=block-formatting-a-node-list><span class=secno>7.4 </span>Block-formatting a node list</h3>
 
+<p class=XXX>Why is this a separate section?  There's only one caller.
+Probably want to merge it back.
+
 <p>To <dfn id=block-format>block-format</dfn> 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> <var title="">input nodes</var> to a
 string <var title="">value</var>:
 
@@ -4717,7 +4448,280 @@
 </ol>
 
 
-<h3 id=the-formatblock-command><span class=secno>7.8 </span><dfn>The <code title="">formatBlock</code> command</dfn></h3>
+<h3 id=the-delete-command><span class=secno>7.8 </span><dfn>The <code title="">delete</code> command</dfn></h3>
+
+<p><a href=#action>Action</a>:
+
+<ol>
+  <li>If the <a href=#active-range>active range</a> is not <code class=external data-anolis-spec=domrange title=dom-Range-collapsed><a href=http://html5.org/specs/dom-range.html#dom-range-collapsed>collapsed</a></code>, <a href=#delete-the-contents>delete the contents</a>
+  of the <a href=#active-range>active range</a> and abort these steps.
+
+  <p class=XXX>Maybe we sometimes want to do the following even if it isn't
+  collapsed?  WebKit seems to do some normalization on the range before
+  deciding whether it's collapsed, and that sounds like a good idea.
+
+  <li>Let <var title="">node</var> and <var title="">offset</var> be the <a href=#active-range>active
+  range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
+
+  <!-- First go up as high as possible within the current block, then drill
+  down to the lowest possible level, in the hopes that we'll wind up at the end
+  of a text node, or maybe in a br or hr. -->
+  <li>Repeat the following steps:
+
+  <ol>
+    <!--
+    If there's an invisible node somewhere, Firefox 5.0a2 removes that node and
+    then stops, so each backspace removes one invisible node.  All others
+    remove the invisible node and then continue on looking for something
+    visible to remove.  The spec follows the latter behavior, since it makes
+    more sense to the user.  Of course, the definition of "invisible node" is
+    not necessarily anything like the spec's.
+    -->
+    <li>If <var title="">offset</var> is zero and <var title="">node</var>'s <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>
+    is an <a href=#editable>editable</a> <a href=#invisible-node>invisible node</a>, remove
+    <var title="">node</var>'s <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> from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+    <li>Otherwise, if <var title="">node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+    <var title="">offset</var> &minus; 1 and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is an <a href=#editable>editable</a>
+    <a href=#invisible-node>invisible node</a>, remove that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> from <var title="">node</var>,
+    then subtract one from <var title="">offset</var>.
+
+    <li>Otherwise, if <var title="">offset</var> is zero and <var title="">node</var> is not a
+    <a href=#prohibited-paragraph-child>prohibited paragraph child</a>, or if <var title="">node</var> is an
+    <a href=#invisible-node>invisible node</a>, set <var title="">offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of
+    <var title="">node</var>, then 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>.
+
+    <li>Otherwise, if <var title="">node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+    <var title="">offset</var> &minus; 1 and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is not a <a href=#prohibited-paragraph-child>prohibited
+    paragraph child</a> or a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or an <code class=external data-anolis-spec=html title="the img element"><a href=http://www.whatwg.org/html/#the-img-element>img</a></code>, set <var title="">node</var> to
+    that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>, then set <var title="">offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of
+    <var title="">node</var>.
+
+    <li>Otherwise, break from this loop.
+  </ol>
+
+  <!--
+  At this point, node cannot be an invisible node.  There are three cases:
+
+  1) offset is zero and node is a prohibited paragraph child.  Then we'll
+  usually merge with the previous block if one exists.
+
+  2) offset is not zero, node is not a prohibited paragraph child, and node
+  does not have a child with index offset - 1.  The only way this is possible
+  is if node has a length greater than zero but no children, which implies it's
+  a text or comment or PI.  Comments and PIs are invisible nodes, so it must be
+  a text node.  We delete the previous character.
+
+  3) offset is not zero, and the child of node with index offset - 1 is a
+  prohibited paragraph child or a br or an img.  Then we'll usually merge the
+  offsetth child of node with the last descendant of the offset - 1st.
+  -->
+
+  <li>If <var title="">node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node and <var title="">offset</var> is not zero,
+  call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+  Then <a href=#delete-the-contents>delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
+  (<var title="">node</var>, <var title="">offset</var> &minus; 1) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>
+  (<var title="">node</var>, <var title="">offset</var>) and abort these steps.
+
+  <!-- At the time of this writing, this should be impossible. -->
+  <li>If <var title="">node</var> is not a <a href=#prohibited-paragraph-child>prohibited paragraph child</a>,
+  abort these steps.
+
+  <li>If <var title="">node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">offset</var>
+  &minus; 1 and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or <code class=external data-anolis-spec=html title="the hr element"><a href=http://www.whatwg.org/html/#the-hr-element>hr</a></code> or <code class=external data-anolis-spec=html title="the img element"><a href=http://www.whatwg.org/html/#the-img-element>img</a></code>, call
+  <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+  Then <a href=#delete-the-contents>delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
+  (<var title="">node</var>, <var title="">offset</var> &minus; 1) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>
+  (<var title="">node</var>, <var title="">offset</var>) and abort these steps.
+
+  <!--
+  If we're at the beginning of a list, we want to outdent the first list item.
+  This doesn't actually match anyone or anything.  Word 2007 and OpenOffice.org
+  3.2.1 Ubuntu just remove the list marker, which is weird and doesn't map well
+  to HTML.  Browsers tend to just merge with the preceding block, which isn't
+  expected.
+  -->
+  <li>If <var title="">node</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> 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> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code> and is 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 its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>:
+
+  <ol>
+    <li>Let <var title="">items</var> be a list of all <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>s that are
+    <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> of <var title="">node</var>.
+
+    <li><a href=#normalize-sublists>Normalize sublists</a> of each <var title="">item</var> in
+    <var title="">items</var>.
+
+    <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><a href=#fix-disallowed-ancestors>Fix disallowed ancestors</a> of <var title="">node</var>.
+
+    <li>Abort these steps.
+  </ol>
+
+  <!-- By this point, we're almost certainly going to merge something, and the
+  only question is what. -->
+  <li>Let <var title="">start node</var> equal <var title="">node</var> and let <var title="">start
+  offset</var> equal <var title="">offset</var>.
+
+  <li>While <var title="">start offset</var> is zero, set <var title="">start offset</var> to the
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <var title="">start node</var> and then set <var title="">start 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>The node at index start offset &minus; 1 might be an invisible
+  node.
+
+  <!--
+  At the beginning of an indented block, outdent it, similar to a list item.
+  Browsers don't do this, word processors do.
+  -->
+  <li>If <var title="">offset</var> is zero, and <var title="">node</var> has an
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container title="ancestor container">ancestor container</a> that is both a <a href=#potential-indentation-element>potential indentation
+  element</a> and 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 <var title="">start node</var>:
+
+  <p class=XXX>This copy-pastes from the outdent command action.  I'm also not
+  totally sure it's correct.
+
+  <ol>
+    <li><a href=#block-extend>Block-extend</a> the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> whose <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and
+    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> are both (<var title="">node</var>, 0), and let <var title="">new range</var> be
+    the result.
+
+    <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 <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="">current 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="">current node</var> to <var title="">node list</var> if the
+    last member of <var title="">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="">current node</var>, and <var title="">current node</var> is
+    <a href=#editable>editable</a> but has no <a href=#editable>editable</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>descendants</a>.
+
+    <li><a href=#outdent>Outdent</a> 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>.
+
+    <li>Abort these steps.
+  </ol>
+
+  <!--
+  This is to avoid stripping a line break from
+
+    foo<br><br><table><tr><td>[]bar</table>
+
+  and similarly for <hr>.  We should just do nothing here.
+  -->
+  <li>If 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>child</a> of <var title="">start node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
+  offset</var> is a <code class=external data-anolis-spec=html title="the table element"><a href=http://www.whatwg.org/html/#the-table-element>table</a></code>, abort these steps.
+
+  <!--
+  If you try backspacing into a table, select it.  This doesn't match any
+  browser; it matches the recommendation of the "behavior when typing in
+  contentEditable elements" document.  The idea is that then you can delete it
+  with a second backspace.
+  -->
+  <li>If <var title="">start node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
+  offset</var> &minus; 1, and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> is a <code class=external data-anolis-spec=html title="the table element"><a href=http://www.whatwg.org/html/#the-table-element>table</a></code>:
+
+  <ol>
+    <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">start node</var>, <var title="">start offset</var>
+    &minus; 1)</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+
+    <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-extend><a href=http://html5.org/specs/dom-range.html#dom-selection-extend>extend(<var title="">start node</var>, <var title="">start offset</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>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+
+    <li>Abort these steps.
+  </ol>
+
+  <!--
+  Special case:
+
+    <p>foo</p><br><p>[]bar</p>
+    -> <p>foo</p><p>[]bar</p>
+
+  and likewise for <hr>.  But with <img> we merge like in other cases:
+
+    <p>foo</p><img><p>[]bar</p>
+    -> <p>foo</p><img>[]bar.
+
+  Browsers don't do this consistently.  Firefox 5.0a2 doesn't seem to do it at
+  all.
+  -->
+  <li>If <var title="">offset</var> is zero; and either 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>child</a> of <var title="">start
+  node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> minus one is an <code class=external data-anolis-spec=html title="the hr element"><a href=http://www.whatwg.org/html/#the-hr-element>hr</a></code>, or
+  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>child</a> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> whose <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> is either a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or not
+  an <a href=#inline-node>inline node</a>:
+
+  <ol>
+    <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the
+    <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+
+    <li><a href=#delete-the-contents>Delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
+    (<var title="">start node</var>, <var title="">start offset</var> &minus; 1) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>
+    (<var title="">start node</var>, <var title="">start offset</var>).
+
+    <li>Abort these steps.
+  </ol>
+
+  <!--
+  If you try backspacing out of a list item, merge it with the previous item,
+  but add a line break.  Then you have to backspace again if you really want
+  them to be on the same line.  This matches Word 2007 and OpenOffice.org 3.2.1
+  Ubuntu, and also matches "behavior when typing in contentEditable elements",
+  but does not match any browser.
+
+  Note that this behavior is quite different from what happens if you actually
+  select the linebreak in between the two lines.  In that case, the blocks are
+  merged as normal.
+
+  Also note that hitting backspace twice will merge with the previous item.
+  This matches OO.org, but Word will outdent the item on subsequent backspaces.
+  Word's behavior doesn't fit well with the way lists work in HTML, and we
+  probably don't want it.
+  -->
+  <li>If 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>child</a> of <var title="">start node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
+  offset</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> 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> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, and that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>'s
+  <code class=external data-anolis-spec=domcore title=dom-Node-firstChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-firstchild>firstChild</a></code> is an <a href=#inline-node>inline node</a>, and <var title="">start offset</var> is
+  not zero:
+
+  <ol>
+    <li>Let <var title="">previous item</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>child</a> of <var title="">start node</var>
+    with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> minus one.
+
+    <!-- If the last child is already a br, we only need to append one extra
+    br.  Otherwise we need to append two, since the first will do nothing. -->
+    <li>If <var title="">previous item</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-lastChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-lastchild>lastChild</a></code> is an <a href=#inline-node>inline
+    node</a> other than 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
+    <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="">previous item</var>.
+
+    <li>If <var title="">previous item</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-lastChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-lastchild>lastChild</a></code> is an <a href=#inline-node>inline
+    node</a>, call <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a> and
+    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="">previous item</var>.
+  </ol>
+
+  <!--
+  When merging adjacent list items, make sure we only merge the items
+  themselves, not any block children.  We want <li><p>foo<li><p>bar to become
+  <li><p>foo<p>bar, not <li><p>foo<br>bar or <li><p>foobar.
+  -->
+  <li>If 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>child</a> of <var title="">start node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
+  offset</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> 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> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, and 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> is
+  also an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> 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> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, set <var title="">start 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-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> &minus; 1, then set
+  <var title="">start offset</var> to <var title="">start node</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a>, then set
+  <var title="">node</var> to <var title="">start node</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>, then set
+  <var title="">offset</var> to 0.
+
+  <!-- General block-merging case. -->
+  <li>Otherwise, while <var title="">start node</var> has a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+  <var title="">start offset</var> minus one, set <var title="">start node</var> to that
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>, then set <var title="">start offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of
+  <var title="">start node</var>.
+
+  <li><a href=#delete-the-contents>Delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
+  (<var title="">start node</var>, <var title="">start offset</var>) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>
+  (<var title="">node</var>, <var title="">offset</var>).
+</ol>
+
+
+<h3 id=the-formatblock-command><span class=secno>7.9 </span><dfn>The <code title="">formatBlock</code> command</dfn></h3>
 <!--
 Tested browser versions: IE9, Firefox 4.0, Chrome 13 dev, Opera 11.10.
 
@@ -4801,7 +4805,7 @@
 </ol>
 
 
-<h3 id=the-indent-command><span class=secno>7.9 </span><dfn>The <code title="">indent</code> command</dfn></h3>
+<h3 id=the-indent-command><span class=secno>7.10 </span><dfn>The <code title="">indent</code> command</dfn></h3>
 <!--
 IE9: Outputs <blockquote style="margin-right: 0px" dir="ltr">, or when
   surrounding RTL blocks, <blockquote style="margin-left: 0px" dir="rtl">.  The
@@ -4904,7 +4908,7 @@
 </ol>
 
 
-<h3 id=the-inserthorizontalrule-command><span class=secno>7.10 </span><dfn>The <code title="">insertHorizontalRule</code> command</dfn></h3>
+<h3 id=the-inserthorizontalrule-command><span class=secno>7.11 </span><dfn>The <code title="">insertHorizontalRule</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -4958,13 +4962,13 @@
 </ol>
 
 
-<h3 id=the-insertorderedlist-command><span class=secno>7.11 </span><dfn>The <code title="">insertOrderedList</code> command</dfn></h3>
+<h3 id=the-insertorderedlist-command><span class=secno>7.12 </span><dfn>The <code title="">insertOrderedList</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
 "ol".
 
 
-<h3 id=the-insertparagraph-command><span class=secno>7.12 </span><dfn>The <code title="">insertParagraph</code> command</dfn></h3>
+<h3 id=the-insertparagraph-command><span class=secno>7.13 </span><dfn>The <code title="">insertParagraph</code> command</dfn></h3>
 <!--
 There are three major behaviors here.  Firefox 5.0a2 behaves identically to
 execCommand("formatBlock", false, "p"), which is not really useful.  IE9
@@ -5214,37 +5218,37 @@
 </ol>
 
 
-<h3 id=the-insertunorderedlist-command><span class=secno>7.13 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
+<h3 id=the-insertunorderedlist-command><span class=secno>7.14 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
 "ul".
 
 
-<h3 id=the-justifycenter-command><span class=secno>7.14 </span><dfn>The <code title="">justifyCenter</code> command</dfn></h3>
+<h3 id=the-justifycenter-command><span class=secno>7.15 </span><dfn>The <code title="">justifyCenter</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "center".
 
 
-<h3 id=the-justifyfull-command><span class=secno>7.15 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
+<h3 id=the-justifyfull-command><span class=secno>7.16 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "justify".
 
 
-<h3 id=the-justifyleft-command><span class=secno>7.16 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
+<h3 id=the-justifyleft-command><span class=secno>7.17 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "left".
 
 
-<h3 id=the-justifyright-command><span class=secno>7.17 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
+<h3 id=the-justifyright-command><span class=secno>7.18 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "right".
 
 
-<h3 id=the-outdent-command><span class=secno>7.18 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
+<h3 id=the-outdent-command><span class=secno>7.19 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
--- a/implementation.js	Tue Jun 14 13:46:20 2011 -0600
+++ b/implementation.js	Tue Jun 14 14:37:27 2011 -0600
@@ -3,9 +3,19 @@
 var htmlNamespace = "http://www.w3.org/1999/xhtml";
 
 var cssStylingFlag = false;
-var defaultSingleLineContainerName = "p";
-
-// Utility functions
+
+// This is bad :(
+var globalRange = null;
+
+// Commands are stored in a dictionary where we call their actions and such
+var commands = {};
+
+///////////////////////////////////////////////////////////////////////////////
+////////////////////////////// Utility functions //////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+//@{
+
+
 function nextNode(node) {
 	if (node.hasChildNodes()) {
 		return node.firstChild;
@@ -192,9 +202,14 @@
 	return ns === null
 		|| ns === htmlNamespace;
 }
-
-
-// Functions for stuff in DOM Range
+//@}
+
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////// DOM Range functions /////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+//@{
+
 function getNodeIndex(node) {
 	if (!node.parentNode) {
 		// No preceding siblings, so . . .
@@ -429,10 +444,17 @@
 		"currentColor": false,
 	}[color];
 }
-
-
-// Things defined in the edit command spec (i.e., the interesting stuff)
-
+//@}
+
+
+//////////////////////////////////////////////////////////////////////////////
+/////////////////////////// Edit command functions ///////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////
+///// Common definitions /////
+//////////////////////////////
+//@{
 
 // "An HTML element is an Element whose namespace is the HTML namespace."
 //
@@ -461,6 +483,338 @@
 		&& ["inline", "inline-block", "inline-table"].indexOf(getComputedStyle(node).display) != -1));
 }
 
+// "An editing host is a node that is either an Element with a contenteditable
+// attribute set to the true state, or the Element child of a Document whose
+// designMode is enabled."
+function isEditingHost(node) {
+	return node
+		&& node.nodeType == Node.ELEMENT_NODE
+		&& (node.contentEditable == "true"
+		|| (node.parentNode
+		&& node.parentNode.nodeType == Node.DOCUMENT_NODE
+		&& node.parentNodedesignMode == "on"));
+}
+
+// "Something is editable if it is a node which is not an editing host, does
+// not have a contenteditable attribute set to the false state, and whose
+// parent is an editing host or editable."
+function isEditable(node) {
+	// This is slightly a lie, because we're excluding non-HTML elements with
+	// contentEditable attributes.
+	return node
+		&& !isEditingHost(node)
+		&& (node.nodeType != Node.ELEMENT_NODE || node.contentEditable != "false")
+		&& (isEditingHost(node.parentNode) || isEditable(node.parentNode));
+}
+
+// Helper function, not defined in the spec
+function hasEditableDescendants(node) {
+	for (var i = 0; i < node.childNodes.length; i++) {
+		if (isEditable(node.childNodes[i])
+		|| hasEditableDescendants(node.childNodes[i])) {
+			return true;
+		}
+	}
+	return false;
+}
+
+// "The editing host of node is null if node is neither editable nor an editing
+// host; node itself, if node is an editing host; or the nearest ancestor of
+// node that is an editing host, if node is editable."
+function getEditingHostOf(node) {
+	if (isEditingHost(node)) {
+		return node;
+	} else if (isEditable(node)) {
+		var ancestor = node.parentNode;
+		while (!isEditingHost(ancestor)) {
+			ancestor = ancestor.parentNode;
+		}
+		return ancestor;
+	} else {
+		return null;
+	}
+}
+
+// "Two nodes are in the same editing host if the editing host of the first is
+// non-null and the same as the editing host of the second."
+function inSameEditingHost(node1, node2) {
+	return getEditingHostOf(node1)
+		&& getEditingHostOf(node1) == getEditingHostOf(node2);
+}
+
+// "A prohibited paragraph child name is "address", "article", "aside",
+// "blockquote", "caption", "center", "col", "colgroup", "details", "dd",
+// "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
+// "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
+// "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
+// "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or
+// "xmp"."
+var prohibitedParagraphChildNames = ["address", "article", "aside",
+	"blockquote", "caption", "center", "col", "colgroup", "details", "dd",
+	"dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
+	"form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
+	"listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
+	"summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul",
+	"xmp"];
+
+// "A prohibited paragraph child is an HTML element whose local name is a
+// prohibited paragraph child name."
+function isProhibitedParagraphChild(node) {
+	return isHtmlElement(node, prohibitedParagraphChildNames);
+}
+
+// "A visible node is a node that either is a prohibited paragraph child, or a
+// Text node whose data is not empty, or a br or img, or any node with a
+// descendant that is a visible node."
+function isVisibleNode(node) {
+	if (!node) {
+		return false;
+	}
+	if (isProhibitedParagraphChild(node)
+	|| (node.nodeType == Node.TEXT_NODE && node.length)
+	|| isHtmlElement(node, ["br", "img"])) {
+		return true;
+	}
+	for (var i = 0; i < node.childNodes.length; i++) {
+		if (isVisibleNode(node.childNodes[i])) {
+			return true;
+		}
+	}
+	return false;
+}
+
+// "An invisible node is a node that is not a visible node."
+function isInvisibleNode(node) {
+	return node && !isVisibleNode(node);
+}
+
+//@}
+
+/////////////////////////////
+///// Common algorithms /////
+/////////////////////////////
+
+///// Assorted common algorithms /////
+//@{
+
+function isAllowedChild(child, parent_) {
+	// "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
+	// an HTML element with local name equal to one of those, and child is a
+	// Text node whose data does not consist solely of space characters, return
+	// false."
+	if ((["colgroup", "table", "tbody", "tfoot", "thead", "tr"].indexOf(parent_) != -1
+	|| isHtmlElement(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"]))
+	&& typeof child == "object"
+	&& child.nodeType == Node.TEXT_NODE
+	&& !/^[ \t\n\f\r]*$/.test(child.data)) {
+		return false;
+	}
+
+	// "If parent is "script", "style", "plaintext", or "xmp", or an HTML
+	// element with local name equal to one of those, and child is not a Text
+	// node, return false."
+	if ((["script", "style", "plaintext", "xmp"].indexOf(parent_) != -1
+	|| isHtmlElement(parent_, ["script", "style", "plaintext", "xmp"]))
+	&& (typeof child != "object" || child.nodeType != Node.TEXT_NODE)) {
+		return false;
+	}
+
+	// "If child is a Document, DocumentFragment, or DocumentType, return
+	// false."
+	if (typeof child == "object"
+	&& (child.nodeType == Node.DOCUMENT_NODE
+	|| child.nodeType == Node.DOCUMENT_FRAGMENT_NODE
+	|| child.nodeType == Node.DOCUMENT_TYPE_NODE)) {
+		return false;
+	}
+
+	// "If child is an HTML element, set child to the local name of child."
+	if (isHtmlElement(child)) {
+		child = child.tagName.toLowerCase();
+	}
+
+	// "If child is not a string, return true."
+	if (typeof child != "string") {
+		return true;
+	}
+
+	// "If parent is an HTML element:"
+	if (isHtmlElement(parent_)) {
+		// "If child is "a", and parent or some ancestor of parent is an a,
+		// return false."
+		//
+		// "If child is a prohibited paragraph child name and parent or some
+		// ancestor of parent is a p, return false."
+		//
+		// "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
+		// some ancestor of parent is an HTML element with local name "h1",
+		// "h2", "h3", "h4", "h5", or "h6", return false."
+		var ancestor = parent_;
+		while (ancestor) {
+			if (child == "a" && isHtmlElement(ancestor, "a")) {
+				return false;
+			}
+			if (prohibitedParagraphChildNames.indexOf(child) != -1
+			&& isHtmlElement(ancestor, "p")) {
+				return false;
+			}
+			if (/^h[1-6]$/.test(child)
+			&& isHtmlElement(ancestor)
+			&& /^H[1-6]$/.test(ancestor.tagName)) {
+				return false;
+			}
+			ancestor = ancestor.parentNode;
+		}
+
+		// "Let parent be the local name of parent."
+		parent_ = parent_.tagName.toLowerCase();
+	}
+
+	// "If parent is an Element or DocumentFragment, return true."
+	if (typeof parent_ == "object"
+	&& (parent_.nodeType == Node.ELEMENT_NODE
+	|| parent_.nodeType == Node.DOCUMENT_FRAGMENT_NODE)) {
+		return true;
+	}
+
+	// "If parent is not a string, return false."
+	if (typeof parent_ != "string") {
+		return false;
+	}
+
+	// "If parent is in the following table, then return true if child is
+	// listed as an allowed child, and false otherwise."
+	switch (parent_) {
+		case "colgroup":
+			return child == "col";
+		case "table":
+			return ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1;
+		case "tbody":
+		case "thead":
+		case "tfoot":
+			return ["td", "th", "tr"].indexOf(child) != -1;
+		case "tr":
+			return ["td", "th"].indexOf(child) != -1;
+	}
+
+	// "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
+	// "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
+	// false."
+	if (["body", "caption", "col", "colgroup", "frame", "frameset", "head",
+	"html", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1) {
+		return false;
+	}
+
+	// "If child is "dd" or "dt" and parent is not "dl", return false."
+	if (["dd", "dt"].indexOf(child) != -1
+	&& parent_ != "dl") {
+		return false;
+	}
+
+	// "If child is "li" and parent is not "ol" or "ul", return false."
+	if (child == "li"
+	&& parent_ != "ol"
+	&& parent_ != "ul") {
+		return false;
+	}
+
+	// "If parent is in the following table and child is listed as a prohibited
+	// child, return false."
+	var table = [
+		[["a"], ["a"]],
+		[["dd", "dt"], ["dd", "dt"]],
+		[["h1", "h2", "h3", "h4", "h5", "h6"], ["h1", "h2", "h3", "h4", "h5", "h6"]],
+		[["li"], ["li"]],
+		[["nobr"], ["nobr"]],
+		[["p"], prohibitedParagraphChildNames],
+		[["td", "th"], ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]],
+	];
+	for (var i = 0; i < table.length; i++) {
+		if (table[i][0].indexOf(parent_) != -1
+		&& table[i][1].indexOf(child) != -1) {
+			return false;
+		}
+	}
+
+	// "Return true."
+	return true;
+}
+
+function movePreservingRanges(node, newParent, newIndex) {
+	// For convenience, I allow newIndex to be -1 to mean "insert at the end".
+	if (newIndex == -1) {
+		newIndex = newParent.childNodes.length;
+	}
+
+	// "When the user agent is to move a Node to a new location, preserving
+	// ranges, it must remove the Node from its original parent (if any), then
+	// insert it in the new location. In doing so, however, it must ignore the
+	// regular range mutation rules, and instead follow these rules:"
+
+	// "Let node be the moved Node, old parent and old index be the old parent
+	// (which may be null) and index, and new parent and new index be the new
+	// parent and index."
+	var oldParent = node.parentNode;
+	var oldIndex = getNodeIndex(node);
+
+	// We only even attempt to preserve the global range object, not every
+	// range out there (the latter is probably impossible).
+	var start = [globalRange.startContainer, globalRange.startOffset];
+	var end = [globalRange.endContainer, globalRange.endOffset];
+
+	// "If a boundary point's node is the same as or a descendant of node,
+	// leave it unchanged, so it moves to the new location."
+	//
+	// No modifications necessary.
+
+	// "If a boundary point's node is new parent and its offset is greater than
+	// new index, add one to its offset."
+	if (globalRange.startContainer == newParent
+	&& globalRange.startOffset > newIndex) {
+		start[1]++;
+	}
+	if (globalRange.endContainer == newParent
+	&& globalRange.endOffset > newIndex) {
+		end[1]++;
+	}
+
+	// "If a boundary point's node is old parent and its offset is old index or
+	// old index + 1, set its node to new parent and add new index − old index
+	// to its offset."
+	if (globalRange.startContainer == oldParent
+	&& (globalRange.startOffset == oldIndex
+	|| globalRange.startOffset == oldIndex + 1)) {
+		start[0] = newParent;
+		start[1] += newIndex - oldIndex;
+	}
+	if (globalRange.endContainer == oldParent
+	&& (globalRange.endOffset == oldIndex
+	|| globalRange.endOffset == oldIndex + 1)) {
+		end[0] = newParent;
+		end[1] += newIndex - oldIndex;
+	}
+
+	// "If a boundary point's node is old parent and its offset is greater than
+	// old index + 1, subtract one from its offset."
+	if (globalRange.startContainer == oldParent
+	&& globalRange.startOffset > oldIndex + 1) {
+		start[1]--;
+	}
+	if (globalRange.endContainer == oldParent
+	&& globalRange.endOffset > oldIndex + 1) {
+		end[1]--;
+	}
+
+	// Now actually move it and preserve the range.
+	if (newParent.childNodes.length == newIndex) {
+		newParent.appendChild(node);
+	} else {
+		newParent.insertBefore(node, newParent.childNodes[newIndex]);
+	}
+	globalRange.setStart(start[0], start[1]);
+	globalRange.setEnd(end[0], end[1]);
+}
+
 function setTagName(element, newName) {
 	// "If element is an HTML element with local name equal to new name, return
 	// element."
@@ -499,6 +853,52 @@
 	return replacementElement;
 }
 
+function removeExtraneousLineBreaksBefore(node) {
+	// "If node is not an Element, or it is an inline node, do nothing and
+	// abort these steps."
+	if (!node
+	|| node.nodeType != Node.ELEMENT_NODE
+	|| isInlineNode(node)) {
+		return;
+	}
+
+	// "If the previousSibling of node is a br, and the previousSibling of the
+	// previousSibling of node is an inline node that is not a br, remove the
+	// previousSibling of node from its parent."
+	if (isHtmlElement(node.previousSibling, "BR")
+	&& isInlineNode(node.previousSibling.previousSibling)
+	&& !isHtmlElement(node.previousSibling.previousSibling, "BR")) {
+		node.parentNode.removeChild(node.previousSibling);
+	}
+}
+
+function removeExtraneousLineBreaksAtTheEndOf(node) {
+	// "If node is not an Element, or it is an inline node, do nothing and
+	// abort these steps."
+	if (!node
+	|| node.nodeType != Node.ELEMENT_NODE
+	|| isInlineNode(node)) {
+		return;
+	}
+
+	// "If node has at least two children, and its last child is a br, and its
+	// second-to-last child is an inline node that is not a br, remove the last
+	// child of node from node."
+	if (node.childNodes.length >= 2
+	&& isHtmlElement(node.lastChild, "BR")
+	&& isInlineNode(node.lastChild.previousSibling)
+	&& !isHtmlElement(node.lastChild.previousSibling, "BR")) {
+		node.removeChild(node.lastChild);
+	}
+}
+
+// "To remove extraneous line breaks from a node, first remove extraneous line
+// breaks before it, then remove extraneous line breaks at the end of it."
+function removeExtraneousLineBreaksFrom(node) {
+	removeExtraneousLineBreaksBefore(node);
+	removeExtraneousLineBreaksAtTheEndOf(node);
+}
+
 function splitParent(nodeList) {
 	// "Let original parent be the parent of the first member of node list."
 	var originalParent = nodeList[0].parentNode;
@@ -613,6 +1013,11 @@
 	splitParent([].slice.call(node.childNodes));
 }
 
+//@}
+
+///// Wrapping a list of nodes /////
+//@{
+
 function wrap(nodeList, siblingCriteria, newParentInstructions) {
 	// "If node list is empty, or the first member of node list is not
 	// editable, return null and abort these steps."
@@ -759,14 +1164,10 @@
 	return newParent;
 }
 
-// "This is defined to be the first range in the Selection given by calling
-// getSelection() on the context object, or null if there is no such range."
-function getActiveRange() {
-	if (getSelection().rangeCount) {
-		return getSelection().getRangeAt(0);
-	}
-	return null;
-}
+//@}
+
+///// Deleting the contents of a range /////
+//@{
 
 function deleteContents(node1, offset1, node2, offset2) {
 	var range;
@@ -1194,298 +1595,15 @@
 	}
 }
 
-function removeExtraneousLineBreaksBefore(node) {
-	// "If node is not an Element, or it is an inline node, do nothing and
-	// abort these steps."
-	if (!node
-	|| node.nodeType != Node.ELEMENT_NODE
-	|| isInlineNode(node)) {
-		return;
-	}
-
-	// "If the previousSibling of node is a br, and the previousSibling of the
-	// previousSibling of node is an inline node that is not a br, remove the
-	// previousSibling of node from its parent."
-	if (isHtmlElement(node.previousSibling, "BR")
-	&& isInlineNode(node.previousSibling.previousSibling)
-	&& !isHtmlElement(node.previousSibling.previousSibling, "BR")) {
-		node.parentNode.removeChild(node.previousSibling);
-	}
-}
-
-function removeExtraneousLineBreaksAtTheEndOf(node) {
-	// "If node is not an Element, or it is an inline node, do nothing and
-	// abort these steps."
-	if (!node
-	|| node.nodeType != Node.ELEMENT_NODE
-	|| isInlineNode(node)) {
-		return;
-	}
-
-	// "If node has at least two children, and its last child is a br, and its
-	// second-to-last child is an inline node that is not a br, remove the last
-	// child of node from node."
-	if (node.childNodes.length >= 2
-	&& isHtmlElement(node.lastChild, "BR")
-	&& isInlineNode(node.lastChild.previousSibling)
-	&& !isHtmlElement(node.lastChild.previousSibling, "BR")) {
-		node.removeChild(node.lastChild);
-	}
-}
-
-// "To remove extraneous line breaks from a node, first remove extraneous line
-// breaks before it, then remove extraneous line breaks at the end of it."
-function removeExtraneousLineBreaksFrom(node) {
-	removeExtraneousLineBreaksBefore(node);
-	removeExtraneousLineBreaksAtTheEndOf(node);
-}
-
-// "An editing host is a node that is either an Element with a contenteditable
-// attribute set to the true state, or the Element child of a Document whose
-// designMode is enabled."
-function isEditingHost(node) {
-	return node
-		&& node.nodeType == Node.ELEMENT_NODE
-		&& (node.contentEditable == "true"
-		|| (node.parentNode
-		&& node.parentNode.nodeType == Node.DOCUMENT_NODE
-		&& node.parentNodedesignMode == "on"));
-}
-
-// "Something is editable if it is a node which is not an editing host, does
-// not have a contenteditable attribute set to the false state, and whose
-// parent is an editing host or editable."
-function isEditable(node) {
-	// This is slightly a lie, because we're excluding non-HTML elements with
-	// contentEditable attributes.
-	return node
-		&& !isEditingHost(node)
-		&& (node.nodeType != Node.ELEMENT_NODE || node.contentEditable != "false")
-		&& (isEditingHost(node.parentNode) || isEditable(node.parentNode));
-}
-
-// "The editing host of node is null if node is neither editable nor an editing
-// host; node itself, if node is an editing host; or the nearest ancestor of
-// node that is an editing host, if node is editable."
-function getEditingHostOf(node) {
-	if (isEditingHost(node)) {
-		return node;
-	} else if (isEditable(node)) {
-		var ancestor = node.parentNode;
-		while (!isEditingHost(ancestor)) {
-			ancestor = ancestor.parentNode;
-		}
-		return ancestor;
-	} else {
-		return null;
-	}
-}
-
-// "Two nodes are in the same editing host if the editing host of the first is
-// non-null and the same as the editing host of the second."
-function inSameEditingHost(node1, node2) {
-	return getEditingHostOf(node1)
-		&& getEditingHostOf(node1) == getEditingHostOf(node2);
-}
-
-// "A prohibited paragraph child name is "address", "article", "aside",
-// "blockquote", "caption", "center", "col", "colgroup", "details", "dd",
-// "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
-// "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
-// "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
-// "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or
-// "xmp"."
-var prohibitedParagraphChildNames = ["address", "article", "aside",
-	"blockquote", "caption", "center", "col", "colgroup", "details", "dd",
-	"dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
-	"form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
-	"listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
-	"summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul",
-	"xmp"];
-
-// "A prohibited paragraph child is an HTML element whose local name is a
-// prohibited paragraph child name."
-function isProhibitedParagraphChild(node) {
-	return isHtmlElement(node, prohibitedParagraphChildNames);
-}
-
-// "A visible node is a node that either is a prohibited paragraph child, or a
-// Text node whose data is not empty, or a br or img, or any node with a
-// descendant that is a visible node."
-function isVisibleNode(node) {
-	if (!node) {
-		return false;
-	}
-	if (isProhibitedParagraphChild(node)
-	|| (node.nodeType == Node.TEXT_NODE && node.length)
-	|| isHtmlElement(node, ["br", "img"])) {
-		return true;
-	}
-	for (var i = 0; i < node.childNodes.length; i++) {
-		if (isVisibleNode(node.childNodes[i])) {
-			return true;
-		}
-	}
-	return false;
-}
-
-// "An invisible node is a node that is not a visible node."
-function isInvisibleNode(node) {
-	return node && !isVisibleNode(node);
-}
-
-function isAllowedChild(child, parent_) {
-	// "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
-	// an HTML element with local name equal to one of those, and child is a
-	// Text node whose data does not consist solely of space characters, return
-	// false."
-	if ((["colgroup", "table", "tbody", "tfoot", "thead", "tr"].indexOf(parent_) != -1
-	|| isHtmlElement(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"]))
-	&& typeof child == "object"
-	&& child.nodeType == Node.TEXT_NODE
-	&& !/^[ \t\n\f\r]*$/.test(child.data)) {
-		return false;
-	}
-
-	// "If parent is "script", "style", "plaintext", or "xmp", or an HTML
-	// element with local name equal to one of those, and child is not a Text
-	// node, return false."
-	if ((["script", "style", "plaintext", "xmp"].indexOf(parent_) != -1
-	|| isHtmlElement(parent_, ["script", "style", "plaintext", "xmp"]))
-	&& (typeof child != "object" || child.nodeType != Node.TEXT_NODE)) {
-		return false;
-	}
-
-	// "If child is a Document, DocumentFragment, or DocumentType, return
-	// false."
-	if (typeof child == "object"
-	&& (child.nodeType == Node.DOCUMENT_NODE
-	|| child.nodeType == Node.DOCUMENT_FRAGMENT_NODE
-	|| child.nodeType == Node.DOCUMENT_TYPE_NODE)) {
-		return false;
-	}
-
-	// "If child is an HTML element, set child to the local name of child."
-	if (isHtmlElement(child)) {
-		child = child.tagName.toLowerCase();
-	}
-
-	// "If child is not a string, return true."
-	if (typeof child != "string") {
-		return true;
-	}
-
-	// "If parent is an HTML element:"
-	if (isHtmlElement(parent_)) {
-		// "If child is "a", and parent or some ancestor of parent is an a,
-		// return false."
-		//
-		// "If child is a prohibited paragraph child name and parent or some
-		// ancestor of parent is a p, return false."
-		//
-		// "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
-		// some ancestor of parent is an HTML element with local name "h1",
-		// "h2", "h3", "h4", "h5", or "h6", return false."
-		var ancestor = parent_;
-		while (ancestor) {
-			if (child == "a" && isHtmlElement(ancestor, "a")) {
-				return false;
-			}
-			if (prohibitedParagraphChildNames.indexOf(child) != -1
-			&& isHtmlElement(ancestor, "p")) {
-				return false;
-			}
-			if (/^h[1-6]$/.test(child)
-			&& isHtmlElement(ancestor)
-			&& /^H[1-6]$/.test(ancestor.tagName)) {
-				return false;
-			}
-			ancestor = ancestor.parentNode;
-		}
-
-		// "Let parent be the local name of parent."
-		parent_ = parent_.tagName.toLowerCase();
-	}
-
-	// "If parent is an Element or DocumentFragment, return true."
-	if (typeof parent_ == "object"
-	&& (parent_.nodeType == Node.ELEMENT_NODE
-	|| parent_.nodeType == Node.DOCUMENT_FRAGMENT_NODE)) {
-		return true;
-	}
-
-	// "If parent is not a string, return false."
-	if (typeof parent_ != "string") {
-		return false;
-	}
-
-	// "If parent is in the following table, then return true if child is
-	// listed as an allowed child, and false otherwise."
-	switch (parent_) {
-		case "colgroup":
-			return child == "col";
-		case "table":
-			return ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1;
-		case "tbody":
-		case "thead":
-		case "tfoot":
-			return ["td", "th", "tr"].indexOf(child) != -1;
-		case "tr":
-			return ["td", "th"].indexOf(child) != -1;
-	}
-
-	// "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
-	// "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
-	// false."
-	if (["body", "caption", "col", "colgroup", "frame", "frameset", "head",
-	"html", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1) {
-		return false;
-	}
-
-	// "If child is "dd" or "dt" and parent is not "dl", return false."
-	if (["dd", "dt"].indexOf(child) != -1
-	&& parent_ != "dl") {
-		return false;
-	}
-
-	// "If child is "li" and parent is not "ol" or "ul", return false."
-	if (child == "li"
-	&& parent_ != "ol"
-	&& parent_ != "ul") {
-		return false;
-	}
-
-	// "If parent is in the following table and child is listed as a prohibited
-	// child, return false."
-	var table = [
-		[["a"], ["a"]],
-		[["dd", "dt"], ["dd", "dt"]],
-		[["h1", "h2", "h3", "h4", "h5", "h6"], ["h1", "h2", "h3", "h4", "h5", "h6"]],
-		[["li"], ["li"]],
-		[["nobr"], ["nobr"]],
-		[["p"], prohibitedParagraphChildNames],
-		[["td", "th"], ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]],
-	];
-	for (var i = 0; i < table.length; i++) {
-		if (table[i][0].indexOf(parent_) != -1
-		&& table[i][1].indexOf(child) != -1) {
-			return false;
-		}
-	}
-
-	// "Return true."
-	return true;
-}
-
-function hasEditableDescendants(node) {
-	for (var i = 0; i < node.childNodes.length; i++) {
-		if (isEditable(node.childNodes[i])
-		|| hasEditableDescendants(node.childNodes[i])) {
-			return true;
-		}
-	}
-	return false;
-}
+//@}
+
+
+//////////////////////////////////////
+///// Inline formatting commands /////
+//////////////////////////////////////
+
+///// Inline formatting command definitions /////
+//@{
 
 /**
  * "A Node is effectively contained in a Range if either it is contained in the
@@ -1558,9 +1676,173 @@
 	].indexOf(node.tagName.toLowerCase()) != -1;
 }
 
-/**
- * "effective value" per edit command spec
- */
+// "A modifiable element is a b, em, i, s, span, strong, sub, sup, or u element
+// with no attributes except possibly style; or a font element with no
+// attributes except possibly style, color, face, and/or size; or an a element
+// with no attributes except possibly style and/or href."
+function isModifiableElement(node) {
+	if (!isHtmlElement(node)) {
+		return false;
+	}
+
+	if (["B", "EM", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) != -1) {
+		if (node.attributes.length == 0) {
+			return true;
+		}
+
+		if (node.attributes.length == 1
+		&& node.hasAttribute("style")) {
+			return true;
+		}
+	}
+
+	if (node.tagName == "FONT" || node.tagName == "A") {
+		var numAttrs = node.attributes.length;
+
+		if (node.hasAttribute("style")) {
+			numAttrs--;
+		}
+
+		if (node.tagName == "FONT") {
+			if (node.hasAttribute("color")) {
+				numAttrs--;
+			}
+
+			if (node.hasAttribute("face")) {
+				numAttrs--;
+			}
+
+			if (node.hasAttribute("size")) {
+				numAttrs--;
+			}
+		}
+
+		if (node.tagName == "A"
+		&& node.hasAttribute("href")) {
+			numAttrs--;
+		}
+
+		if (numAttrs == 0) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+function isSimpleModifiableElement(node) {
+	// "A simple modifiable element is an HTML element for which at least one
+	// of the following holds:"
+	if (!isHtmlElement(node)) {
+		return false;
+	}
+
+	// Only these elements can possibly be a simple modifiable element.
+	if (["A", "B", "EM", "FONT", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) == -1) {
+		return false;
+	}
+
+	// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
+	// element with no attributes."
+	if (node.attributes.length == 0) {
+		return true;
+	}
+
+	// If it's got more than one attribute, everything after this fails.
+	if (node.attributes.length > 1) {
+		return false;
+	}
+
+	// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
+	// element with exactly one attribute, which is style, which sets no CSS
+	// properties (including invalid or unrecognized properties)."
+	//
+	// Not gonna try for invalid or unrecognized.
+	if (node.hasAttribute("style")
+	&& node.style.length == 0) {
+		return true;
+	}
+
+	// "It is an a element with exactly one attribute, which is href."
+	if (node.tagName == "A"
+	&& node.hasAttribute("href")) {
+		return true;
+	}
+
+	// "It is a font element with exactly one attribute, which is either color,
+	// face, or size."
+	if (node.tagName == "FONT"
+	&& (node.hasAttribute("color")
+	|| node.hasAttribute("face")
+	|| node.hasAttribute("size")
+	)) {
+		return true;
+	}
+
+	// "It is a b or strong element with exactly one attribute, which is style,
+	// and the style attribute sets exactly one CSS property (including invalid
+	// or unrecognized properties), which is "font-weight"."
+	if ((node.tagName == "B" || node.tagName == "STRONG")
+	&& node.hasAttribute("style")
+	&& node.style.length == 1
+	&& node.style.fontWeight != "") {
+		return true;
+	}
+
+	// "It is an i or em element with exactly one attribute, which is style,
+	// and the style attribute sets exactly one CSS property (including invalid
+	// or unrecognized properties), which is "font-style"."
+	if ((node.tagName == "I" || node.tagName == "EM")
+	&& node.hasAttribute("style")
+	&& node.style.length == 1
+	&& node.style.fontStyle != "") {
+		return true;
+	}
+
+	// "It is a sub or sub element with exactly one attribute, which is style,
+	// and the style attribute sets exactly one CSS property (including invalid
+	// or unrecognized properties), which is "vertical-align"."
+	if ((node.tagName == "SUB" || node.tagName == "SUP")
+	&& node.hasAttribute("style")
+	&& node.style.length == 1
+	&& node.style.verticalAlign != "") {
+		return true;
+	}
+
+	// "It is an a, font, or span element with exactly one attribute, which is
+	// style, and the style attribute sets exactly one CSS property (including
+	// invalid or unrecognized properties), and that property is not
+	// "text-decoration"."
+	if ((node.tagName == "A" || node.tagName == "FONT" || node.tagName == "SPAN")
+	&& node.hasAttribute("style")
+	&& node.style.length == 1
+	&& node.style.textDecoration == "") {
+		return true;
+	}
+
+	// "It is an a, font, s, span, strike, or u element with exactly one
+	// attribute, which is style, and the style attribute sets exactly one CSS
+	// property (including invalid or unrecognized properties), which is
+	// "text-decoration", which is set to "line-through" or "underline" or
+	// "overline" or "none"."
+	if (["A", "FONT", "S", "SPAN", "STRIKE", "U"].indexOf(node.tagName) != -1
+	&& node.hasAttribute("style")
+	&& node.style.length == 1
+	&& (node.style.textDecoration == "line-through"
+	|| node.style.textDecoration == "underline"
+	|| node.style.textDecoration == "overline"
+	|| node.style.textDecoration == "none")) {
+		return true;
+	}
+
+	return false;
+}
+
+//@}
+
+///// Assorted inline formatting command definitions /////
+//@{
+
 function getEffectiveValue(node, command) {
 	// "If neither node nor its parent is an Element, return null."
 	if (node.nodeType != Node.ELEMENT_NODE
@@ -1705,9 +1987,6 @@
 	return getComputedStyle(node)[getRelevantCssProperty(command)];
 }
 
-/**
- * "specified value" per edit command spec
- */
 function getSpecifiedValue(element, command) {
 	// "If command is "hiliteColor" and element's display property does not
 	// compute to "inline", return null."
@@ -1907,242 +2186,10 @@
 	movePreservingRanges(node, candidate, -1);
 }
 
-// "A modifiable element is a b, em, i, s, span, strong, sub, sup, or u element
-// with no attributes except possibly style; or a font element with no
-// attributes except possibly style, color, face, and/or size; or an a element
-// with no attributes except possibly style and/or href."
-function isModifiableElement(node) {
-	if (!isHtmlElement(node)) {
-		return false;
-	}
-
-	if (["B", "EM", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) != -1) {
-		if (node.attributes.length == 0) {
-			return true;
-		}
-
-		if (node.attributes.length == 1
-		&& node.hasAttribute("style")) {
-			return true;
-		}
-	}
-
-	if (node.tagName == "FONT" || node.tagName == "A") {
-		var numAttrs = node.attributes.length;
-
-		if (node.hasAttribute("style")) {
-			numAttrs--;
-		}
-
-		if (node.tagName == "FONT") {
-			if (node.hasAttribute("color")) {
-				numAttrs--;
-			}
-
-			if (node.hasAttribute("face")) {
-				numAttrs--;
-			}
-
-			if (node.hasAttribute("size")) {
-				numAttrs--;
-			}
-		}
-
-		if (node.tagName == "A"
-		&& node.hasAttribute("href")) {
-			numAttrs--;
-		}
-
-		if (numAttrs == 0) {
-			return true;
-		}
-	}
-
-	return false;
-}
-
-function isSimpleModifiableElement(node) {
-	// "A simple modifiable element is an HTML element for which at least one
-	// of the following holds:"
-	if (!isHtmlElement(node)) {
-		return false;
-	}
-
-	// Only these elements can possibly be a simple modifiable element.
-	if (["A", "B", "EM", "FONT", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) == -1) {
-		return false;
-	}
-
-	// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
-	// element with no attributes."
-	if (node.attributes.length == 0) {
-		return true;
-	}
-
-	// If it's got more than one attribute, everything after this fails.
-	if (node.attributes.length > 1) {
-		return false;
-	}
-
-	// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
-	// element with exactly one attribute, which is style, which sets no CSS
-	// properties (including invalid or unrecognized properties)."
-	//
-	// Not gonna try for invalid or unrecognized.
-	if (node.hasAttribute("style")
-	&& node.style.length == 0) {
-		return true;
-	}
-
-	// "It is an a element with exactly one attribute, which is href."
-	if (node.tagName == "A"
-	&& node.hasAttribute("href")) {
-		return true;
-	}
-
-	// "It is a font element with exactly one attribute, which is either color,
-	// face, or size."
-	if (node.tagName == "FONT"
-	&& (node.hasAttribute("color")
-	|| node.hasAttribute("face")
-	|| node.hasAttribute("size")
-	)) {
-		return true;
-	}
-
-	// "It is a b or strong element with exactly one attribute, which is style,
-	// and the style attribute sets exactly one CSS property (including invalid
-	// or unrecognized properties), which is "font-weight"."
-	if ((node.tagName == "B" || node.tagName == "STRONG")
-	&& node.hasAttribute("style")
-	&& node.style.length == 1
-	&& node.style.fontWeight != "") {
-		return true;
-	}
-
-	// "It is an i or em element with exactly one attribute, which is style,
-	// and the style attribute sets exactly one CSS property (including invalid
-	// or unrecognized properties), which is "font-style"."
-	if ((node.tagName == "I" || node.tagName == "EM")
-	&& node.hasAttribute("style")
-	&& node.style.length == 1
-	&& node.style.fontStyle != "") {
-		return true;
-	}
-
-	// "It is a sub or sub element with exactly one attribute, which is style,
-	// and the style attribute sets exactly one CSS property (including invalid
-	// or unrecognized properties), which is "vertical-align"."
-	if ((node.tagName == "SUB" || node.tagName == "SUP")
-	&& node.hasAttribute("style")
-	&& node.style.length == 1
-	&& node.style.verticalAlign != "") {
-		return true;
-	}
-
-	// "It is an a, font, or span element with exactly one attribute, which is
-	// style, and the style attribute sets exactly one CSS property (including
-	// invalid or unrecognized properties), and that property is not
-	// "text-decoration"."
-	if ((node.tagName == "A" || node.tagName == "FONT" || node.tagName == "SPAN")
-	&& node.hasAttribute("style")
-	&& node.style.length == 1
-	&& node.style.textDecoration == "") {
-		return true;
-	}
-
-	// "It is an a, font, s, span, strike, or u element with exactly one
-	// attribute, which is style, and the style attribute sets exactly one CSS
-	// property (including invalid or unrecognized properties), which is
-	// "text-decoration", which is set to "line-through" or "underline" or
-	// "overline" or "none"."
-	if (["A", "FONT", "S", "SPAN", "STRIKE", "U"].indexOf(node.tagName) != -1
-	&& node.hasAttribute("style")
-	&& node.style.length == 1
-	&& (node.style.textDecoration == "line-through"
-	|| node.style.textDecoration == "underline"
-	|| node.style.textDecoration == "overline"
-	|| node.style.textDecoration == "none")) {
-		return true;
-	}
-
-	return false;
-}
-
-function movePreservingRanges(node, newParent, newIndex) {
-	// For convenience, I allow newIndex to be -1 to mean "insert at the end".
-	if (newIndex == -1) {
-		newIndex = newParent.childNodes.length;
-	}
-
-	// "When the user agent is to move a Node to a new location, preserving
-	// ranges, it must remove the Node from its original parent (if any), then
-	// insert it in the new location. In doing so, however, it must ignore the
-	// regular range mutation rules, and instead follow these rules:"
-
-	// "Let node be the moved Node, old parent and old index be the old parent
-	// (which may be null) and index, and new parent and new index be the new
-	// parent and index."
-	var oldParent = node.parentNode;
-	var oldIndex = getNodeIndex(node);
-
-	// We only even attempt to preserve the global range object, not every
-	// range out there (the latter is probably impossible).
-	var start = [globalRange.startContainer, globalRange.startOffset];
-	var end = [globalRange.endContainer, globalRange.endOffset];
-
-	// "If a boundary point's node is the same as or a descendant of node,
-	// leave it unchanged, so it moves to the new location."
-	//
-	// No modifications necessary.
-
-	// "If a boundary point's node is new parent and its offset is greater than
-	// new index, add one to its offset."
-	if (globalRange.startContainer == newParent
-	&& globalRange.startOffset > newIndex) {
-		start[1]++;
-	}
-	if (globalRange.endContainer == newParent
-	&& globalRange.endOffset > newIndex) {
-		end[1]++;
-	}
-
-	// "If a boundary point's node is old parent and its offset is old index or
-	// old index + 1, set its node to new parent and add new index − old index
-	// to its offset."
-	if (globalRange.startContainer == oldParent
-	&& (globalRange.startOffset == oldIndex
-	|| globalRange.startOffset == oldIndex + 1)) {
-		start[0] = newParent;
-		start[1] += newIndex - oldIndex;
-	}
-	if (globalRange.endContainer == oldParent
-	&& (globalRange.endOffset == oldIndex
-	|| globalRange.endOffset == oldIndex + 1)) {
-		end[0] = newParent;
-		end[1] += newIndex - oldIndex;
-	}
-
-	// "If a boundary point's node is old parent and its offset is greater than
-	// old index + 1, subtract one from its offset."
-	if (globalRange.startContainer == oldParent
-	&& globalRange.startOffset > oldIndex + 1) {
-		start[1]--;
-	}
-	if (globalRange.endContainer == oldParent
-	&& globalRange.endOffset > oldIndex + 1) {
-		end[1]--;
-	}
-
-	// Now actually move it and preserve the range.
-	if (newParent.childNodes.length == newIndex) {
-		newParent.appendChild(node);
-	} else {
-		newParent.insertBefore(node, newParent.childNodes[newIndex]);
-	}
-	globalRange.setStart(start[0], start[1]);
-	globalRange.setEnd(end[0], end[1]);
-}
+//@}
+
+///// Decomposing a range into nodes /////
+//@{
 
 function decomposeRange(range) {
 	// "If range's start and end are the same, return an empty list."
@@ -2216,312 +2263,10 @@
 	return ret;
 }
 
-function normalizeSublists(item) {
-	// "If item is not an li or it is not editable or its parent is not
-	// editable, abort these steps."
-	if (!isHtmlElement(item, "LI")
-	|| !isEditable(item)
-	|| !isEditable(item.parentNode)) {
-		return;
-	}
-
-	// "Let new item be null."
-	var newItem = null;
-
-	// "While item has an ol or ul child:"
-	while ([].some.call(item.childNodes, function (node) { return isHtmlElement(node, ["OL", "UL"]) })) {
-		// "Let child be the last child of item."
-		var child = item.lastChild;
-
-		// "If child is an ol or ul, or new item is null and child is a Text
-		// node whose data consists of zero of more space characters:"
-		if (isHtmlElement(child, ["OL", "UL"])
-		|| (!newItem && child.nodeType == Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {
-			// "Set new item to null."
-			newItem = null;
-
-			// "Insert child into the parent of item immediately following
-			// item, preserving ranges."
-			movePreservingRanges(child, item.parentNode, 1 + getNodeIndex(item));
-
-		// "Otherwise:"
-		} else {
-			// "If new item is null, let new item be the result of calling
-			// createElement("li") on the ownerDocument of item, then insert
-			// new item into the parent of item immediately after item."
-			if (!newItem) {
-				newItem = item.ownerDocument.createElement("li");
-				item.parentNode.insertBefore(newItem, item.nextSibling);
-			}
-
-			// "Insert child into new item as its first child, preserving
-			// ranges."
-			movePreservingRanges(child, newItem, 0);
-		}
-	}
-}
-
-function blockExtendRange(range) {
-	// "Let start node, start offset, end node, and end offset be the start
-	// and end nodes and offsets of the range."
-	var startNode = range.startContainer;
-	var startOffset = range.startOffset;
-	var endNode = range.endContainer;
-	var endOffset = range.endOffset;
-
-	// "If some ancestor container of start node is an li, set start offset to
-	// the index of the last such li in tree order, and set start node to that
-	// li's parent."
-	for (
-		var ancestorContainer = startNode;
-		ancestorContainer;
-		ancestorContainer = ancestorContainer.parentNode
-	) {
-		if (isHtmlElement(ancestorContainer, "LI")) {
-			startOffset = getNodeIndex(ancestorContainer);
-			startNode = ancestorContainer.parentNode;
-			break;
-		}
-	}
-
-	// "Repeat the following steps:"
-	while (true) {
-		// "If start node is a Text or Comment node or start offset is 0,
-		// set start offset to the index of start node and then set start
-		// node to its parent."
-		if (startNode.nodeType == Node.TEXT_NODE
-		|| startNode.nodeType == Node.COMMENT_NODE
-		|| startOffset == 0) {
-			startOffset = getNodeIndex(startNode);
-			startNode = startNode.parentNode;
-
-		// "Otherwise, if start offset is start node's length and start node's
-		// last child is an inline node that's not a br, subtract one from
-		// start offset."
-		} else if (startOffset == getNodeLength(startNode)
-		&& isInlineNode(startNode.lastChild)
-		&& !isHtmlElement(startNode.lastChild, "br")) {
-			startOffset--;
-
-		// "Otherwise, if start node has a child with index start offset, and
-		// that child and its previousSibling are both inline nodes and the
-		// previousSibling isn't a br, subtract one from start offset."
-		} else if (startOffset < startNode.childNodes.length
-		&& isInlineNode(startNode.childNodes[startOffset])
-		&& isInlineNode(startNode.childNodes[startOffset].previousSibling)
-		&& !isHtmlElement(startNode.childNodes[startOffset].previousSibling, "BR")) {
-			startOffset--;
-
-		// "Otherwise, break from this loop."
-		} else {
-			break;
-		}
-	}
-
-	// "If some ancestor container of end node is an li, set end offset to one
-	// plus the index of the last such li in tree order, and set end node to
-	// that li's parent."
-	for (
-		var ancestorContainer = endNode;
-		ancestorContainer;
-		ancestorContainer = ancestorContainer.parentNode
-	) {
-		if (isHtmlElement(ancestorContainer, "LI")) {
-			endOffset = 1 + getNodeIndex(ancestorContainer);
-			endNode = ancestorContainer.parentNode;
-			break;
-		}
-	}
-
-	// "Repeat the following steps:"
-	while (true) {
-		// "If end node is a Text or Comment node or end offset is equal to the
-		// length of end node, set end offset to one plus the index of end node
-		// and then set end node to its parent."
-		if (endNode.nodeType == Node.TEXT_NODE
-		|| endNode.nodeType == Node.COMMENT_NODE
-		|| endOffset == getNodeLength(endNode)) {
-			endOffset = 1 + getNodeIndex(endNode);
-			endNode = endNode.parentNode;
-
-		// "Otherwise, if end offset is 0 and end node's first child is an
-		// inline node that's not a br, add one to end offset."
-		} else if (endOffset == 0
-		&& isInlineNode(endNode.firstChild)
-		&& !isHtmlElement(endNode.firstChild, "br")) {
-			endOffset++;
-
-		// "Otherwise, if end node has a child with index end offset, and that
-		// child and its previousSibling are both inline nodes, and the
-		// previousSibling isn't a br, add one to end offset."
-		} else if (endOffset < endNode.childNodes.length
-		&& isInlineNode(endNode.childNodes[endOffset])
-		&& isInlineNode(endNode.childNodes[endOffset].previousSibling)
-		&& !isHtmlElement(endNode.childNodes[endOffset], "BR")) {
-			endOffset++;
-
-		// "Otherwise, break from this loop."
-		} else {
-			break;
-		}
-	}
-
-	// "If the child of end node with index end offset is a br, add one to end
-	// offset."
-	if (isHtmlElement(endNode.childNodes[endOffset], "BR")) {
-		endOffset++;
-	}
-
-	// "While end offset is equal to the length of end node, set end offset to
-	// one plus the index of end node and then set end node to its parent."
-	while (endOffset == getNodeLength(endNode)) {
-		endOffset = 1 + getNodeIndex(endNode);
-		endNode = endNode.parentNode;
-	}
-
-	// "Let new range be a new range whose start and end nodes and offsets
-	// are start node, start offset, end node, and end offset."
-	var newRange = startNode.ownerDocument.createRange();
-	newRange.setStart(startNode, startOffset);
-	newRange.setEnd(endNode, endOffset);
-
-	// "Return new range."
-	return newRange;
-}
-
-function blockFormat(inputNodes, value) {
-	// "For each node in input nodes, 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 < inputNodes.length; i++) {
-		var node = inputNodes[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;
-			}
-		}
-	}
-
-	// "Let node list be a list of nodes, initially empty."
-	var nodeList = [];
-
-	// "For each node in input nodes, fix prohibited paragraph descendants of
-	// node, and append the resulting nodes to node list."
-	for (var i = 0; i < inputNodes.length; i++) {
-		nodeList = nodeList.concat(fixProhibitedParagraphDescendants(inputNodes[i]));
-	}
-
-	// "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;
-			}
-
-			// "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 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 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) });
-		}
-
-	// "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."
-			wrap(sublist,
-				function(node) { return isHtmlElement(node, value.toUpperCase()) && !node.attributes.length },
-				function() { return document.createElement(value) });
-		}
-	}
-}
+//@}
+
+///// Clearing an element's value /////
+//@{
 
 function clearValue(element, command) {
 	// "If element's specified value for command is null, return the empty
@@ -2642,6 +2387,11 @@
 	return [newElement];
 }
 
+//@}
+
+///// Pushing down values /////
+//@{
+
 function pushDownValues(node, command, newValue) {
 	// "If node's parent is not an Element, abort this algorithm."
 	if (!node.parentNode
@@ -2746,6 +2496,11 @@
 	}
 }
 
+//@}
+
+///// Forcing the value of a node /////
+//@{
+
 function forceValue(node, command, newValue) {
 	// "If node's parent is null, abort this algorithm."
 	if (!node.parentNode) {
@@ -3070,6 +2825,11 @@
 	}
 }
 
+//@}
+
+///// Setting the value of a node /////
+//@{
+
 function setNodeValue(node, command, newValue) {
 	// "If node is not editable:"
 	if (!isEditable(node)) {
@@ -3117,8 +2877,1119 @@
 	}
 }
 
-// This is bad :(
-var globalRange = null;
+//@}
+
+
+/////////////////////////////////////
+///// Block formatting commands /////
+/////////////////////////////////////
+
+///// Block formatting command definitions /////
+//@{
+
+// "A potential indentation element is either a blockquote, or a div that has a
+// style attribute that sets "margin" or some subproperty of it."
+function isPotentialIndentationElement(node) {
+	if (!isHtmlElement(node)) {
+		return false;
+	}
+
+	if (node.tagName == "BLOCKQUOTE") {
+		return true;
+	}
+
+	if (node.tagName != "DIV") {
+		return false;
+	}
+
+	for (var i = 0; i < node.style.length; i++) {
+		// Approximate check
+		if (/^(-[a-z]+-)?margin/.test(node.style[i])) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+// "An indentation element is a potential indentation element that has no
+// attributes other than one or more of
+//
+//   * "a style attribute that sets no properties other than "margin", "border",
+//     "padding", or subproperties of those;
+//   * "a class attribute;
+//   * "a dir attribute."
+function isIndentationElement(node) {
+	if (!isPotentialIndentationElement(node)) {
+		return false;
+	}
+
+	if (node.tagName != "BLOCKQUOTE" && node.tagName != "DIV") {
+		return false;
+	}
+
+	for (var i = 0; i < node.attributes.length; i++) {
+		if (!isHtmlNamespace(node.attributes[i].namespaceURI)
+		|| ["style", "class", "dir"].indexOf(node.attributes[i].name) == -1) {
+			return false;
+		}
+	}
+
+	for (var i = 0; i < node.style.length; i++) {
+		// This is approximate, but it works well enough for my purposes.
+		if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+// "A non-list single-line container is an HTML element with local name
+// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre"."
+function isNonListSingleLineContainer(node) {
+	return isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5",
+		"h6", "p", "pre"]);
+}
+
+// "A single-line container is either a non-list single-line container, or an
+// HTML element with local name "li", "dt", or "dd"."
+function isSingleLineContainer(node) {
+	return isNonListSingleLineContainer(node)
+		|| isHtmlElement(node, ["li", "dt", "dd"]);
+}
+
+// "The default single-line container name is "p"."
+var defaultSingleLineContainerName = "p";
+
+//@}
+
+///// Assorted block formatting algorithm commands /////
+//@{
+
+function fixDisallowedAncestors(node) {
+	// "If node is an li and its parent is not an ol, unset its value
+	// attribute, if set."
+	if (isHtmlElement(node, "li")
+	&& !isHtmlElement(node.parentNode, "ol")) {
+		node.removeAttribute("value");
+	}
+
+	// "If node is an li and its parent is not an ol or ul, or node is a dt or
+	// dd and its parent is not a dl:"
+	if ((isHtmlElement(node, "li")
+	&& !isHtmlElement(node.parentNode, ["ol", "ul"]))
+	|| (isHtmlElement(node, ["dt", "dd"])
+	&& !isHtmlElement(node.parentNode, "dl"))) {
+		// "Set the tag name of node to the default single-line container name,
+		// and let node be the result."
+		node = setTagName(node, defaultSingleLineContainerName);
+
+		// "Fix disallowed ancestors of node."
+		fixDisallowedAncestors(node);
+
+		// "Fix prohibited paragraph descendants of node."
+		fixProhibitedParagraphDescendants(node);
+
+		// "Abort these steps."
+		return;
+	}
+
+	// "If node is an allowed child of its parent, or it is not an allowed
+	// child of any of its ancestors in the same editing host, abort these
+	// steps and do nothing."
+	if (isAllowedChild(node, node.parentNode)) {
+		return;
+	}
+	var ancestor = node.parentNode;
+	var hasAllowedAncestor = false;
+	while (inSameEditingHost(node, ancestor)) {
+		if (isAllowedChild(node, ancestor)) {
+			hasAllowedAncestor = true;
+			break;
+		}
+		ancestor = ancestor.parentNode;
+	}
+	if (!hasAllowedAncestor) {
+		return;
+	}
+
+	// "While node is not an allowed child of its parent, split the parent of
+	// the one-node list consisting of node."
+	while (!isAllowedChild(node, node.parentNode)) {
+		splitParent([node]);
+	}
+}
+
+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) {
+		return;
+	}
+
+	// "Let first node be the first member of node list."
+	var firstNode = nodeList[0];
+
+	// "If first node's parent is an ol or ul:"
+	if (isHtmlElement(firstNode.parentNode, ["OL", "UL"])) {
+		// "Let tag be the local name of the parent of first node."
+		var tag = firstNode.parentNode.tagName;
+
+		// "Wrap node list, with sibling criteria matching only HTML elements
+		// with local name tag and new parent instructions returning the result
+		// of calling createElement(tag) on the ownerDocument of first node."
+		wrap(nodeList,
+			function(node) { return isHtmlElement(node, tag) },
+			function() { return firstNode.ownerDocument.createElement(tag) });
+
+		// "Abort these steps."
+		return;
+	}
+
+	// "Wrap node list, with sibling criteria matching any indentation element,
+	// and new parent instructions to return the result of calling
+	// createElement("blockquote") on the ownerDocument of first node. Let new
+	// parent be the result."
+	var newParent = wrap(nodeList,
+		function(node) { return isIndentationElement(node) },
+		function() { return firstNode.ownerDocument.createElement("blockquote") });
+
+	// "Fix disallowed ancestors of new parent."
+	fixDisallowedAncestors(newParent);
+}
+
+function normalizeSublists(item) {
+	// "If item is not an li or it is not editable or its parent is not
+	// editable, abort these steps."
+	if (!isHtmlElement(item, "LI")
+	|| !isEditable(item)
+	|| !isEditable(item.parentNode)) {
+		return;
+	}
+
+	// "Let new item be null."
+	var newItem = null;
+
+	// "While item has an ol or ul child:"
+	while ([].some.call(item.childNodes, function (node) { return isHtmlElement(node, ["OL", "UL"]) })) {
+		// "Let child be the last child of item."
+		var child = item.lastChild;
+
+		// "If child is an ol or ul, or new item is null and child is a Text
+		// node whose data consists of zero of more space characters:"
+		if (isHtmlElement(child, ["OL", "UL"])
+		|| (!newItem && child.nodeType == Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {
+			// "Set new item to null."
+			newItem = null;
+
+			// "Insert child into the parent of item immediately following
+			// item, preserving ranges."
+			movePreservingRanges(child, item.parentNode, 1 + getNodeIndex(item));
+
+		// "Otherwise:"
+		} else {
+			// "If new item is null, let new item be the result of calling
+			// createElement("li") on the ownerDocument of item, then insert
+			// new item into the parent of item immediately after item."
+			if (!newItem) {
+				newItem = item.ownerDocument.createElement("li");
+				item.parentNode.insertBefore(newItem, item.nextSibling);
+			}
+
+			// "Insert child into new item as its first child, preserving
+			// ranges."
+			movePreservingRanges(child, newItem, 0);
+		}
+	}
+}
+
+//@}
+
+///// Block-extending a range /////
+//@{
+
+function blockExtendRange(range) {
+	// "Let start node, start offset, end node, and end offset be the start
+	// and end nodes and offsets of the range."
+	var startNode = range.startContainer;
+	var startOffset = range.startOffset;
+	var endNode = range.endContainer;
+	var endOffset = range.endOffset;
+
+	// "If some ancestor container of start node is an li, set start offset to
+	// the index of the last such li in tree order, and set start node to that
+	// li's parent."
+	for (
+		var ancestorContainer = startNode;
+		ancestorContainer;
+		ancestorContainer = ancestorContainer.parentNode
+	) {
+		if (isHtmlElement(ancestorContainer, "LI")) {
+			startOffset = getNodeIndex(ancestorContainer);
+			startNode = ancestorContainer.parentNode;
+			break;
+		}
+	}
+
+	// "Repeat the following steps:"
+	while (true) {
+		// "If start node is a Text or Comment node or start offset is 0,
+		// set start offset to the index of start node and then set start
+		// node to its parent."
+		if (startNode.nodeType == Node.TEXT_NODE
+		|| startNode.nodeType == Node.COMMENT_NODE
+		|| startOffset == 0) {
+			startOffset = getNodeIndex(startNode);
+			startNode = startNode.parentNode;
+
+		// "Otherwise, if start offset is start node's length and start node's
+		// last child is an inline node that's not a br, subtract one from
+		// start offset."
+		} else if (startOffset == getNodeLength(startNode)
+		&& isInlineNode(startNode.lastChild)
+		&& !isHtmlElement(startNode.lastChild, "br")) {
+			startOffset--;
+
+		// "Otherwise, if start node has a child with index start offset, and
+		// that child and its previousSibling are both inline nodes and the
+		// previousSibling isn't a br, subtract one from start offset."
+		} else if (startOffset < startNode.childNodes.length
+		&& isInlineNode(startNode.childNodes[startOffset])
+		&& isInlineNode(startNode.childNodes[startOffset].previousSibling)
+		&& !isHtmlElement(startNode.childNodes[startOffset].previousSibling, "BR")) {
+			startOffset--;
+
+		// "Otherwise, break from this loop."
+		} else {
+			break;
+		}
+	}
+
+	// "If some ancestor container of end node is an li, set end offset to one
+	// plus the index of the last such li in tree order, and set end node to
+	// that li's parent."
+	for (
+		var ancestorContainer = endNode;
+		ancestorContainer;
+		ancestorContainer = ancestorContainer.parentNode
+	) {
+		if (isHtmlElement(ancestorContainer, "LI")) {
+			endOffset = 1 + getNodeIndex(ancestorContainer);
+			endNode = ancestorContainer.parentNode;
+			break;
+		}
+	}
+
+	// "Repeat the following steps:"
+	while (true) {
+		// "If end node is a Text or Comment node or end offset is equal to the
+		// length of end node, set end offset to one plus the index of end node
+		// and then set end node to its parent."
+		if (endNode.nodeType == Node.TEXT_NODE
+		|| endNode.nodeType == Node.COMMENT_NODE
+		|| endOffset == getNodeLength(endNode)) {
+			endOffset = 1 + getNodeIndex(endNode);
+			endNode = endNode.parentNode;
+
+		// "Otherwise, if end offset is 0 and end node's first child is an
+		// inline node that's not a br, add one to end offset."
+		} else if (endOffset == 0
+		&& isInlineNode(endNode.firstChild)
+		&& !isHtmlElement(endNode.firstChild, "br")) {
+			endOffset++;
+
+		// "Otherwise, if end node has a child with index end offset, and that
+		// child and its previousSibling are both inline nodes, and the
+		// previousSibling isn't a br, add one to end offset."
+		} else if (endOffset < endNode.childNodes.length
+		&& isInlineNode(endNode.childNodes[endOffset])
+		&& isInlineNode(endNode.childNodes[endOffset].previousSibling)
+		&& !isHtmlElement(endNode.childNodes[endOffset], "BR")) {
+			endOffset++;
+
+		// "Otherwise, break from this loop."
+		} else {
+			break;
+		}
+	}
+
+	// "If the child of end node with index end offset is a br, add one to end
+	// offset."
+	if (isHtmlElement(endNode.childNodes[endOffset], "BR")) {
+		endOffset++;
+	}
+
+	// "While end offset is equal to the length of end node, set end offset to
+	// one plus the index of end node and then set end node to its parent."
+	while (endOffset == getNodeLength(endNode)) {
+		endOffset = 1 + getNodeIndex(endNode);
+		endNode = endNode.parentNode;
+	}
+
+	// "Let new range be a new range whose start and end nodes and offsets
+	// are start node, start offset, end node, and end offset."
+	var newRange = startNode.ownerDocument.createRange();
+	newRange.setStart(startNode, startOffset);
+	newRange.setEnd(endNode, endOffset);
+
+	// "Return new range."
+	return newRange;
+}
+
+//@}
+
+///// Block-formatting a node list /////
+//@{
+
+function blockFormat(inputNodes, value) {
+	// "For each node in input nodes, 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 < inputNodes.length; i++) {
+		var node = inputNodes[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;
+			}
+		}
+	}
+
+	// "Let node list be a list of nodes, initially empty."
+	var nodeList = [];
+
+	// "For each node in input nodes, fix prohibited paragraph descendants of
+	// node, and append the resulting nodes to node list."
+	for (var i = 0; i < inputNodes.length; i++) {
+		nodeList = nodeList.concat(fixProhibitedParagraphDescendants(inputNodes[i]));
+	}
+
+	// "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;
+			}
+
+			// "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 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 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) });
+		}
+
+	// "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."
+			wrap(sublist,
+				function(node) { return isHtmlElement(node, value.toUpperCase()) && !node.attributes.length },
+				function() { return document.createElement(value) });
+		}
+	}
+}
+
+//@}
+
+///// Outdenting a node /////
+//@{
+
+function outdentNode(node) {
+	// "If node is not editable, abort these steps."
+	if (!isEditable(node)) {
+		return;
+	}
+
+	// "If node is an indentation element, remove node, preserving its
+	// descendants.  Then abort these steps."
+	if (isIndentationElement(node)) {
+		removePreservingDescendants(node);
+		return;
+	}
+
+	// "If node is a potential indentation element:"
+	if (isPotentialIndentationElement(node)) {
+		// "Unset the class and dir attributes of node, if any."
+		node.removeAttribute("class");
+		node.removeAttribute("dir");
+
+		// "Unset the margin, padding, and border CSS properties of node."
+		node.style.margin = "";
+		node.style.padding = "";
+		node.style.border = "";
+		if (node.getAttribute("style") == "") {
+			node.removeAttribute("style");
+		}
+
+		// "Set the tag name of node to "div"."
+		setTagName(node, "div");
+
+		// "Abort these steps."
+		return;
+	}
+
+	// "Let current ancestor be node's parent."
+	var currentAncestor = node.parentNode;
+
+	// "Let ancestor list be a list of nodes, initially empty."
+	var ancestorList = [];
+
+	// "While current ancestor is an editable Element that is not an
+	// indentation element, append current ancestor to ancestor list and then
+	// set current ancestor to its parent."
+	while (isEditable(currentAncestor)
+	&& currentAncestor.nodeType == Node.ELEMENT_NODE
+	&& !isIndentationElement(currentAncestor)) {
+		ancestorList.push(currentAncestor);
+		currentAncestor = currentAncestor.parentNode;
+	}
+
+	// "If current ancestor is not an editable indentation element:"
+	if (!isEditable(currentAncestor)
+	|| !isIndentationElement(currentAncestor)) {
+		// "Let current ancestor be node's parent."
+		currentAncestor = node.parentNode;
+
+		// "Let ancestor list be the empty list."
+		ancestorList = [];
+
+		// "While current ancestor is an editable Element that is not a
+		// potential indentation element, append current ancestor to ancestor
+		// list and then set current ancestor to its parent."
+		while (isEditable(currentAncestor)
+		&& currentAncestor.nodeType == Node.ELEMENT_NODE
+		&& !isPotentialIndentationElement(currentAncestor)) {
+			ancestorList.push(currentAncestor);
+			currentAncestor = currentAncestor.parentNode;
+		}
+	}
+
+	// "If node is an ol or ul, and either current ancestor is not an editable
+	// potential indentation element or node's parent is an ol or ul:"
+	if (isHtmlElement(node, ["OL", "UL"])
+	&& (!isEditable(currentAncestor)
+	|| !isPotentialIndentationElement(currentAncestor)
+	|| isHtmlElement(node.parentNode, ["OL", "UL"]))) {
+		// "Unset the reversed, start, and type attributes of node, if any are
+		// set."
+		node.removeAttribute("reversed");
+		node.removeAttribute("start");
+		node.removeAttribute("type");
+
+		// "Let children be the children of node."
+		var children = [].slice.call(node.childNodes);
+
+		// "If node has attributes, and its parent or not an ol or ul, set the
+		// tag name of node to "div"."
+		if (node.attributes.length
+		&& !isHtmlElement(node.parentNode, ["OL", "UL"])) {
+			setTagName(node, "div");
+
+		// "Otherwise remove node, preserving its descendants."
+		} else {
+			removePreservingDescendants(node);
+		}
+
+		// "Fix disallowed ancestors of each member of children."
+		for (var i = 0; i < children.length; i++) {
+			fixDisallowedAncestors(children[i]);
+		}
+
+		// "Abort these steps."
+		return;
+	}
+
+	// "If current ancestor is not an editable potential indentation element,
+	// abort these steps."
+	if (!isEditable(currentAncestor)
+	|| !isPotentialIndentationElement(currentAncestor)) {
+		return;
+	}
+
+	// "Append current ancestor to ancestor list."
+	ancestorList.push(currentAncestor);
+
+	// "Let original ancestor be current ancestor."
+	var originalAncestor = currentAncestor;
+
+	// "While ancestor list is not empty:"
+	while (ancestorList.length) {
+		// "Let current ancestor be the last member of ancestor list."
+		//
+		// "Remove the last member of ancestor list."
+		currentAncestor = ancestorList.pop();
+
+		// "Let target be the child of current ancestor that is equal to either
+		// node or the last member of ancestor list."
+		var target = node.parentNode == currentAncestor
+			? node
+			: ancestorList[ancestorList.length - 1];
+
+		// "If target is an inline node that is not a br, and its nextSibling
+		// is a br, remove target's nextSibling from its parent."
+		if (isInlineNode(target)
+		&& !isHtmlElement(target, "BR")
+		&& isHtmlElement(target.nextSibling, "BR")) {
+			target.parentNode.removeChild(target.nextSibling);
+		}
+
+		// "Let preceding siblings be the preceding siblings of target, and let
+		// following siblings be the following siblings of target."
+		var precedingSiblings = [].slice.call(currentAncestor.childNodes, 0, getNodeIndex(target));
+		var followingSiblings = [].slice.call(currentAncestor.childNodes, 1 + getNodeIndex(target));
+
+		// "Indent preceding siblings."
+		indentNodes(precedingSiblings);
+
+		// "Indent following siblings."
+		indentNodes(followingSiblings);
+	}
+
+	// "Outdent original ancestor."
+	outdentNode(originalAncestor);
+}
+
+//@}
+
+///// Toggling lists /////
+//@{
+
+function toggleLists(range, tagName) {
+	tagName = tagName.toUpperCase();
+
+	// "Let other tag name be "ol" if tag name is "ul", and "ul" if tag name is
+	// "ol"."
+	var otherTagName = tagName == "OL" ? "UL" : "OL";
+
+	// "Let items be a list of all lis that are ancestor containers of the
+	// range's start and/or end node."
+	//
+	// Has to be in tree order, remember!
+	var items = [];
+	for (var node = range.endContainer; node != range.commonAncestorContainer; node = node.parentNode) {
+		if (isHtmlElement(node, "LI")) {
+			items.unshift(node);
+		}
+	}
+	for (var node = range.startContainer; node != range.commonAncestorContainer; node = node.parentNode) {
+		if (isHtmlElement(node, "LI")) {
+			items.unshift(node);
+		}
+	}
+	for (var node = range.commonAncestorContainer; node; node = node.parentNode) {
+		if (isHtmlElement(node, "LI")) {
+			items.unshift(node);
+		}
+	}
+
+	// "For each item in items, normalize sublists of item."
+	for (var i = 0; i < items.length; i++) {
+		normalizeSublists(items[i]);
+	}
+
+	// "Block-extend the range, and let new range be the result."
+	var newRange = blockExtendRange(range);
+
+	// "Let node list be a list of nodes, initially empty."
+	var nodeList = [];
+
+	// "For each node node contained in new range, if node is editable; the
+	// last member of node list (if any) is not an ancestor of node; node
+	// is not a potential indentation element; and either node is an ol or
+	// ul, or its parent is an ol or ul, or it is an allowed child of "li";
+	// then append node to node list."
+	for (
+		var node = newRange.startContainer;
+		node != nextNodeDescendants(newRange.endContainer);
+		node = nextNode(node)
+	) {
+		if (isEditable(node)
+		&& isContained(node, newRange)
+		&& (!nodeList.length || !isAncestor(nodeList[nodeList.length - 1], node))
+		&& !isPotentialIndentationElement(node)
+		&& (isHtmlElement(node, ["OL", "UL"])
+		|| isHtmlElement(node.parentNode, ["OL", "UL"])
+		|| isAllowedChild(node, "li"))) {
+			nodeList.push(node);
+		}
+	}
+
+	// "If every member of node list is equal to or the child of an HTML
+	// element with local name tag name, and no member of node list is equal to
+	// or the ancestor of an HTML element with local name other tag name, then
+	// while node list is not empty:"
+	if (nodeList.every(function(node) { return isHtmlElement(node, tagName) || isHtmlElement(node.parentNode, tagName) })
+	&& !nodeList.some(function(node) { return isHtmlElement(node, otherTagName) || node.querySelector(otherTagName) })) {
+		while (nodeList.length) {
+			// "Let sublist be an empty list of nodes."
+			var sublist = [];
+
+			// "Remove the first member from node list and append it to
+			// sublist."
+			sublist.push(nodeList.shift());
+
+			// "If the first member of sublist is an HTML element with local
+			// name tag name, outdent it and continue this loop from the
+			// beginning."
+			if (isHtmlElement(sublist[0], tagName)) {
+				outdentNode(sublist[0]);
+				continue;
+			}
+
+			// "While node list is not empty, and the first member of node list
+			// is the nextSibling of the last member of sublist and is not an
+			// HTML element with local name tag name, remove the first member
+			// from node list and append it to sublist."
+			while (nodeList.length
+			&& nodeList[0] == sublist[sublist.length - 1].nextSibling
+			&& !isHtmlElement(nodeList[0], tagName)) {
+				sublist.push(nodeList.shift());
+			}
+
+			// "Split the parent of sublist."
+			splitParent(sublist);
+
+			// "Fix disallowed ancestors of each member of sublist."
+			for (var i = 0; i < sublist.length; i++) {
+				fixDisallowedAncestors(sublist[i]);
+			}
+		}
+
+	// "Otherwise, while node list is not empty:"
+	} else {
+		while (nodeList.length) {
+			// "Let sublist be an empty list of nodes."
+			var sublist = [];
+
+			// "Remove the first member from 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 last member of sublist and first member of node list are
+			// both inline nodes, and the last member of sublist is not a
+			// br, remove the first member from node list and append it to
+			// sublist."
+			while (nodeList.length
+			&& nodeList[0] == sublist[sublist.length - 1].nextSibling
+			&& isInlineNode(sublist[sublist.length - 1])
+			&& isInlineNode(nodeList[0])
+			&& !isHtmlElement(sublist[sublist.length - 1], "BR")) {
+				sublist.push(nodeList.shift());
+			}
+
+			// "If sublist contains more than one member, wrap sublist, with
+			// sibling criteria matching nothing and new parent instructions
+			// returning the result of calling createElement("li") on the
+			// context object. Let node be the result."
+			var node;
+			if (sublist.length > 1) {
+				node = wrap(sublist,
+					function() { return false },
+					function() { return document.createElement("li") });
+
+			// "Otherwise, let node be the sole member of sublist."
+			} else {
+				node = sublist[0];
+			}
+
+			// "If node is an HTML element with local name other tag name:"
+			if (isHtmlElement(node, otherTagName)) {
+				// "Let children be the children of node."
+				var children = [].slice.call(node.childNodes);
+
+				// "Remove node, preserving its descendants."
+				removePreservingDescendants(node);
+
+				// "Wrap children, with sibling criteria matching any HTML
+				// element with local name tag name and new parent instructions
+				// returning the result of calling createElement(tag name) on
+				// the context object. Let node be the result."
+				node = wrap(children,
+					function(node) { return isHtmlElement(node, tagName) },
+					function() { return document.createElement(tagName) });
+
+				// "Prepend the descendants of node that are HTML elements with
+				// local name other tag name (if any) to node list."
+				nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
+
+				// "Continue from the beginning of this loop."
+				continue;
+			}
+
+			// "If node is a p or div, set the tag name of node to "li",
+			// and let node be the result."
+			if (isHtmlElement(node, ["P", "DIV"])) {
+				node = setTagName(node, "li");
+			}
+
+			// "If node is the child of an HTML element with local name other
+			// tag name:"
+			if (isHtmlElement(node.parentNode, otherTagName)) {
+				// "Split the parent of the one-node list consisting of
+				// node."
+				splitParent([node]);
+
+				// "Wrap the one-node list consisting of node, with sibling
+				// criteria matching any HTML element with local name tag name,
+				// and with new parent instructions returning the result of
+				// calling createElement(tag name) on the context object."
+				wrap([node],
+					function(node) { return isHtmlElement(node, tagName) },
+					function() { return document.createElement(tagName) });
+
+				// "Prepend the descendants of node that are HTML elements with
+				// local name other tag name (if any) to node list."
+				nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
+
+				// "Continue from the beginning of this loop."
+				continue;
+			}
+
+			// "If node is equal to or the child of an HTML element with local
+			// name tag name, prepend the descendants of node that are HTML
+			// elements with local name other tag name (if any) to node list
+			// and continue from the beginning of this loop."
+			if (isHtmlElement(node, tagName)
+			|| isHtmlElement(node.parentNode, tagName)) {
+				nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
+				continue;
+			}
+
+			// "If node is not an li, wrap the one-node list consisting of
+			// node, with sibling criteria matching nothing and new parent
+			// instructions returning the result of calling createElement("li")
+			// on the context object. Set node to the result."
+			if (!isHtmlElement(node, "LI")) {
+				node = wrap([node],
+					function() { return false },
+					function() { return document.createElement("li") });
+			}
+
+			// "Wrap the one-node list consisting of node, with the sibling
+			// criteria matching any HTML element with local name tag name, and
+			// the new parent instructions being the following:"
+			var newParent = wrap([node],
+				function(node) { return isHtmlElement(node, tagName) },
+				function() {
+					// "If the parent of node is not an editable indentation
+					// element, or the previousSibling of the parent of node is
+					// not an editable HTML element with local name tag name,
+					// call createElement(tag name) on the context object and
+					// return the result. Otherwise:"
+					if (!isEditable(node.parentNode)
+					|| !isIndentationElement(node.parentNode)
+					|| !isEditable(node.parentNode.previousSibling)
+					|| !isHtmlElement(node.parentNode.previousSibling, tagName)) {
+						return document.createElement(tagName);
+					}
+
+					// "Let list be the previousSibling of the parent of node."
+					var list = node.parentNode.previousSibling;
+
+					// "Normalize sublists of list's last child."
+					normalizeSublists(list.lastChild);
+
+					// "If list's last child is not an editable HTML element
+					// with local name tag name, call createElement(tag name)
+					// on the context object, and append the result as the last
+					// child of list."
+					if (!isEditable(list.lastChild)
+					|| !isHtmlElement(list.lastChild, tagName)) {
+						list.appendChild(document.createElement(tagName));
+					}
+
+					// "Return the last child of list."
+					return list.lastChild;
+				});
+
+			// "Fix disallowed ancestors of the previous step's result."
+			fixDisallowedAncestors(newParent);
+		}
+	}
+}
+
+//@}
+
+///// Justifying the selection /////
+//@{
+
+function justifySelection(alignment) {
+	// "Block-extend the active range, and let new range be the result."
+	var newRange = blockExtendRange(globalRange);
+
+	// "Let element list be a list of all editable Elements contained in new
+	// range that either has an attribute in the HTML namespace whose local
+	// name is "align", or has a style attribute that sets "text-align", or is
+	// a center."
+	var elementList = collectAllContainedNodes(newRange, function(node) {
+		return node.nodeType == Node.ELEMENT_NODE
+			&& isEditable(node)
+			// Ignoring namespaces here
+			&& (
+				node.hasAttribute("align")
+				|| node.style.textAlign != ""
+				|| isHtmlElement(node, "center")
+			);
+	});
+
+	// "For each element in element list:"
+	for (var i = 0; i < elementList.length; i++) {
+		var element = elementList[i];
+
+		// "If element has an attribute in the HTML namespace whose local name
+		// is "align", remove that attribute."
+		element.removeAttribute("align");
+
+		// "Unset the CSS property "text-align" on element, if it's set by a
+		// style attribute."
+		element.style.textAlign = "";
+		if (element.getAttribute("style") == "") {
+			element.removeAttribute("style");
+		}
+
+		// "If element is a div or center with no attributes, remove it,
+		// preserving its descendants."
+		if (isHtmlElement(element, ["div", "center"])
+		&& !element.attributes.length) {
+			removePreservingDescendants(element);
+		}
+
+		// "If element is a center with one or more attributes, set the tag
+		// name of element to "div"."
+		if (isHtmlElement(element, "center")
+		&& element.attributes.length) {
+			setTagName(element, "div");
+		}
+	}
+
+	// "Block-extend the active range, and let new range be the result."
+	newRange = blockExtendRange(globalRange);
+
+	// "Let node list be a list of nodes, initially empty."
+	var nodeList = [];
+
+	// "For each node node contained in new range, append node to node list if
+	// the last member of node list (if any) is not an ancestor of node; node
+	// is editable; and either node is an Element and the CSS property
+	// "text-align" does not compute to alignment on it, or it is not an
+	// Element, but its parent is an Element, and the CSS property "text-align"
+	// does not compute to alignment on its parent."
+	nodeList = collectContainedNodes(newRange, function(node) {
+		if (!isEditable(node)) {
+			return false;
+		}
+		// Gecko and WebKit have lots of fun here confusing us with
+		// vendor-specific values, and in Gecko's case "start".
+		var element = node.nodeType == Node.ELEMENT_NODE
+			? node
+			: node.parentNode;
+		if (!element || element.nodeType != Node.ELEMENT_NODE) {
+			return false;
+		}
+		var computedAlign = getComputedStyle(element).textAlign
+			.replace(/^-(moz|webkit)-/, "");
+		if (computedAlign == "auto" || computedAlign == "start") {
+			// Depends on directionality.  Note: this is a serious hack.
+			do {
+				var dir = element.dir.toLowerCase();
+				element = element.parentNode;
+			} while (element && element.nodeType == Node.ELEMENT_NODE && dir != "ltr" && dir != "rtl");
+			if (dir == "rtl") {
+				computedAlign = "right";
+			} else {
+				computedAlign = "left";
+			}
+		}
+		return computedAlign != alignment;
+	});
+
+	// "While node list is not empty:"
+	while (nodeList.length) {
+		// "Let sublist be a list of nodes, initially empty."
+		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, remove the first
+		// member of node list and append it to sublist."
+		while (nodeList.length
+		&& nodeList[0] == sublist[sublist.length - 1].nextSibling) {
+			sublist.push(nodeList.shift());
+		}
+
+		// "Wrap sublist. Sibling criteria match any div that has one or both
+		// of the following two attributes, and no other attributes:
+		//
+		//   * "An align attribute whose value is an ASCII case-insensitive
+		//     match for alignment.
+		//   * "A style attribute which sets exactly one CSS property
+		//     (including unrecognized or invalid attributes), which is
+		//     "text-align", which is set to alignment.
+		//
+		// "New parent instructions are to call createElement("div") on the
+		// context object, then set its CSS property "text-align" to alignment,
+		// and return the result."
+		wrap(sublist,
+			function(node) {
+				return isHtmlElement(node, "div")
+					&& [].every.call(node.attributes, function(attr) {
+						return (attr.name == "align" && attr.value.toLowerCase() == alignment)
+							|| (attr.name == "style" && node.style.length == 1 && node.style.textAlign == alignment);
+					});
+			},
+			function() {
+				var newParent = document.createElement("div");
+				newParent.setAttribute("style", "text-align: " + alignment);
+				return newParent;
+			}
+		);
+	}
+}
+
+//@}
 
 function getRelevantCssProperty(command) {
 	var prop = {
@@ -4396,768 +5267,6 @@
 	}
 }
 
-function fixDisallowedAncestors(node) {
-	// "If node is an li and its parent is not an ol, unset its value
-	// attribute, if set."
-	if (isHtmlElement(node, "li")
-	&& !isHtmlElement(node.parentNode, "ol")) {
-		node.removeAttribute("value");
-	}
-
-	// "If node is an li and its parent is not an ol or ul, or node is a dt or
-	// dd and its parent is not a dl:"
-	if ((isHtmlElement(node, "li")
-	&& !isHtmlElement(node.parentNode, ["ol", "ul"]))
-	|| (isHtmlElement(node, ["dt", "dd"])
-	&& !isHtmlElement(node.parentNode, "dl"))) {
-		// "Set the tag name of node to the default single-line container name,
-		// and let node be the result."
-		node = setTagName(node, defaultSingleLineContainerName);
-
-		// "Fix disallowed ancestors of node."
-		fixDisallowedAncestors(node);
-
-		// "Fix prohibited paragraph descendants of node."
-		fixProhibitedParagraphDescendants(node);
-
-		// "Abort these steps."
-		return;
-	}
-
-	// "If node is an allowed child of its parent, or it is not an allowed
-	// child of any of its ancestors in the same editing host, abort these
-	// steps and do nothing."
-	if (isAllowedChild(node, node.parentNode)) {
-		return;
-	}
-	var ancestor = node.parentNode;
-	var hasAllowedAncestor = false;
-	while (inSameEditingHost(node, ancestor)) {
-		if (isAllowedChild(node, ancestor)) {
-			hasAllowedAncestor = true;
-			break;
-		}
-		ancestor = ancestor.parentNode;
-	}
-	if (!hasAllowedAncestor) {
-		return;
-	}
-
-	// "While node is not an allowed child of its parent, split the parent of
-	// the one-node list consisting of node."
-	while (!isAllowedChild(node, node.parentNode)) {
-		splitParent([node]);
-	}
-}
-
-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) {
-		return;
-	}
-
-	// "Let first node be the first member of node list."
-	var firstNode = nodeList[0];
-
-	// "If first node's parent is an ol or ul:"
-	if (isHtmlElement(firstNode.parentNode, ["OL", "UL"])) {
-		// "Let tag be the local name of the parent of first node."
-		var tag = firstNode.parentNode.tagName;
-
-		// "Wrap node list, with sibling criteria matching only HTML elements
-		// with local name tag and new parent instructions returning the result
-		// of calling createElement(tag) on the ownerDocument of first node."
-		wrap(nodeList,
-			function(node) { return isHtmlElement(node, tag) },
-			function() { return firstNode.ownerDocument.createElement(tag) });
-
-		// "Abort these steps."
-		return;
-	}
-
-	// "Wrap node list, with sibling criteria matching any indentation element,
-	// and new parent instructions to return the result of calling
-	// createElement("blockquote") on the ownerDocument of first node. Let new
-	// parent be the result."
-	var newParent = wrap(nodeList,
-		function(node) { return isIndentationElement(node) },
-		function() { return firstNode.ownerDocument.createElement("blockquote") });
-
-	// "Fix disallowed ancestors of new parent."
-	fixDisallowedAncestors(newParent);
-}
-
-function outdentNode(node) {
-	// "If node is not editable, abort these steps."
-	if (!isEditable(node)) {
-		return;
-	}
-
-	// "If node is an indentation element, remove node, preserving its
-	// descendants.  Then abort these steps."
-	if (isIndentationElement(node)) {
-		removePreservingDescendants(node);
-		return;
-	}
-
-	// "If node is a potential indentation element:"
-	if (isPotentialIndentationElement(node)) {
-		// "Unset the class and dir attributes of node, if any."
-		node.removeAttribute("class");
-		node.removeAttribute("dir");
-
-		// "Unset the margin, padding, and border CSS properties of node."
-		node.style.margin = "";
-		node.style.padding = "";
-		node.style.border = "";
-		if (node.getAttribute("style") == "") {
-			node.removeAttribute("style");
-		}
-
-		// "Set the tag name of node to "div"."
-		setTagName(node, "div");
-
-		// "Abort these steps."
-		return;
-	}
-
-	// "Let current ancestor be node's parent."
-	var currentAncestor = node.parentNode;
-
-	// "Let ancestor list be a list of nodes, initially empty."
-	var ancestorList = [];
-
-	// "While current ancestor is an editable Element that is not an
-	// indentation element, append current ancestor to ancestor list and then
-	// set current ancestor to its parent."
-	while (isEditable(currentAncestor)
-	&& currentAncestor.nodeType == Node.ELEMENT_NODE
-	&& !isIndentationElement(currentAncestor)) {
-		ancestorList.push(currentAncestor);
-		currentAncestor = currentAncestor.parentNode;
-	}
-
-	// "If current ancestor is not an editable indentation element:"
-	if (!isEditable(currentAncestor)
-	|| !isIndentationElement(currentAncestor)) {
-		// "Let current ancestor be node's parent."
-		currentAncestor = node.parentNode;
-
-		// "Let ancestor list be the empty list."
-		ancestorList = [];
-
-		// "While current ancestor is an editable Element that is not a
-		// potential indentation element, append current ancestor to ancestor
-		// list and then set current ancestor to its parent."
-		while (isEditable(currentAncestor)
-		&& currentAncestor.nodeType == Node.ELEMENT_NODE
-		&& !isPotentialIndentationElement(currentAncestor)) {
-			ancestorList.push(currentAncestor);
-			currentAncestor = currentAncestor.parentNode;
-		}
-	}
-
-	// "If node is an ol or ul, and either current ancestor is not an editable
-	// potential indentation element or node's parent is an ol or ul:"
-	if (isHtmlElement(node, ["OL", "UL"])
-	&& (!isEditable(currentAncestor)
-	|| !isPotentialIndentationElement(currentAncestor)
-	|| isHtmlElement(node.parentNode, ["OL", "UL"]))) {
-		// "Unset the reversed, start, and type attributes of node, if any are
-		// set."
-		node.removeAttribute("reversed");
-		node.removeAttribute("start");
-		node.removeAttribute("type");
-
-		// "Let children be the children of node."
-		var children = [].slice.call(node.childNodes);
-
-		// "If node has attributes, and its parent or not an ol or ul, set the
-		// tag name of node to "div"."
-		if (node.attributes.length
-		&& !isHtmlElement(node.parentNode, ["OL", "UL"])) {
-			setTagName(node, "div");
-
-		// "Otherwise remove node, preserving its descendants."
-		} else {
-			removePreservingDescendants(node);
-		}
-
-		// "Fix disallowed ancestors of each member of children."
-		for (var i = 0; i < children.length; i++) {
-			fixDisallowedAncestors(children[i]);
-		}
-
-		// "Abort these steps."
-		return;
-	}
-
-	// "If current ancestor is not an editable potential indentation element,
-	// abort these steps."
-	if (!isEditable(currentAncestor)
-	|| !isPotentialIndentationElement(currentAncestor)) {
-		return;
-	}
-
-	// "Append current ancestor to ancestor list."
-	ancestorList.push(currentAncestor);
-
-	// "Let original ancestor be current ancestor."
-	var originalAncestor = currentAncestor;
-
-	// "While ancestor list is not empty:"
-	while (ancestorList.length) {
-		// "Let current ancestor be the last member of ancestor list."
-		//
-		// "Remove the last member of ancestor list."
-		currentAncestor = ancestorList.pop();
-
-		// "Let target be the child of current ancestor that is equal to either
-		// node or the last member of ancestor list."
-		var target = node.parentNode == currentAncestor
-			? node
-			: ancestorList[ancestorList.length - 1];
-
-		// "If target is an inline node that is not a br, and its nextSibling
-		// is a br, remove target's nextSibling from its parent."
-		if (isInlineNode(target)
-		&& !isHtmlElement(target, "BR")
-		&& isHtmlElement(target.nextSibling, "BR")) {
-			target.parentNode.removeChild(target.nextSibling);
-		}
-
-		// "Let preceding siblings be the preceding siblings of target, and let
-		// following siblings be the following siblings of target."
-		var precedingSiblings = [].slice.call(currentAncestor.childNodes, 0, getNodeIndex(target));
-		var followingSiblings = [].slice.call(currentAncestor.childNodes, 1 + getNodeIndex(target));
-
-		// "Indent preceding siblings."
-		indentNodes(precedingSiblings);
-
-		// "Indent following siblings."
-		indentNodes(followingSiblings);
-	}
-
-	// "Outdent original ancestor."
-	outdentNode(originalAncestor);
-}
-
-function toggleLists(range, tagName) {
-	tagName = tagName.toUpperCase();
-
-	// "Let other tag name be "ol" if tag name is "ul", and "ul" if tag name is
-	// "ol"."
-	var otherTagName = tagName == "OL" ? "UL" : "OL";
-
-	// "Let items be a list of all lis that are ancestor containers of the
-	// range's start and/or end node."
-	//
-	// Has to be in tree order, remember!
-	var items = [];
-	for (var node = range.endContainer; node != range.commonAncestorContainer; node = node.parentNode) {
-		if (isHtmlElement(node, "LI")) {
-			items.unshift(node);
-		}
-	}
-	for (var node = range.startContainer; node != range.commonAncestorContainer; node = node.parentNode) {
-		if (isHtmlElement(node, "LI")) {
-			items.unshift(node);
-		}
-	}
-	for (var node = range.commonAncestorContainer; node; node = node.parentNode) {
-		if (isHtmlElement(node, "LI")) {
-			items.unshift(node);
-		}
-	}
-
-	// "For each item in items, normalize sublists of item."
-	for (var i = 0; i < items.length; i++) {
-		normalizeSublists(items[i]);
-	}
-
-	// "Block-extend the range, and let new range be the result."
-	var newRange = blockExtendRange(range);
-
-	// "Let node list be a list of nodes, initially empty."
-	var nodeList = [];
-
-	// "For each node node contained in new range, if node is editable; the
-	// last member of node list (if any) is not an ancestor of node; node
-	// is not a potential indentation element; and either node is an ol or
-	// ul, or its parent is an ol or ul, or it is an allowed child of "li";
-	// then append node to node list."
-	for (
-		var node = newRange.startContainer;
-		node != nextNodeDescendants(newRange.endContainer);
-		node = nextNode(node)
-	) {
-		if (isEditable(node)
-		&& isContained(node, newRange)
-		&& (!nodeList.length || !isAncestor(nodeList[nodeList.length - 1], node))
-		&& !isPotentialIndentationElement(node)
-		&& (isHtmlElement(node, ["OL", "UL"])
-		|| isHtmlElement(node.parentNode, ["OL", "UL"])
-		|| isAllowedChild(node, "li"))) {
-			nodeList.push(node);
-		}
-	}
-
-	// "If every member of node list is equal to or the child of an HTML
-	// element with local name tag name, and no member of node list is equal to
-	// or the ancestor of an HTML element with local name other tag name, then
-	// while node list is not empty:"
-	if (nodeList.every(function(node) { return isHtmlElement(node, tagName) || isHtmlElement(node.parentNode, tagName) })
-	&& !nodeList.some(function(node) { return isHtmlElement(node, otherTagName) || node.querySelector(otherTagName) })) {
-		while (nodeList.length) {
-			// "Let sublist be an empty list of nodes."
-			var sublist = [];
-
-			// "Remove the first member from node list and append it to
-			// sublist."
-			sublist.push(nodeList.shift());
-
-			// "If the first member of sublist is an HTML element with local
-			// name tag name, outdent it and continue this loop from the
-			// beginning."
-			if (isHtmlElement(sublist[0], tagName)) {
-				outdentNode(sublist[0]);
-				continue;
-			}
-
-			// "While node list is not empty, and the first member of node list
-			// is the nextSibling of the last member of sublist and is not an
-			// HTML element with local name tag name, remove the first member
-			// from node list and append it to sublist."
-			while (nodeList.length
-			&& nodeList[0] == sublist[sublist.length - 1].nextSibling
-			&& !isHtmlElement(nodeList[0], tagName)) {
-				sublist.push(nodeList.shift());
-			}
-
-			// "Split the parent of sublist."
-			splitParent(sublist);
-
-			// "Fix disallowed ancestors of each member of sublist."
-			for (var i = 0; i < sublist.length; i++) {
-				fixDisallowedAncestors(sublist[i]);
-			}
-		}
-
-	// "Otherwise, while node list is not empty:"
-	} else {
-		while (nodeList.length) {
-			// "Let sublist be an empty list of nodes."
-			var sublist = [];
-
-			// "Remove the first member from 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 last member of sublist and first member of node list are
-			// both inline nodes, and the last member of sublist is not a
-			// br, remove the first member from node list and append it to
-			// sublist."
-			while (nodeList.length
-			&& nodeList[0] == sublist[sublist.length - 1].nextSibling
-			&& isInlineNode(sublist[sublist.length - 1])
-			&& isInlineNode(nodeList[0])
-			&& !isHtmlElement(sublist[sublist.length - 1], "BR")) {
-				sublist.push(nodeList.shift());
-			}
-
-			// "If sublist contains more than one member, wrap sublist, with
-			// sibling criteria matching nothing and new parent instructions
-			// returning the result of calling createElement("li") on the
-			// context object. Let node be the result."
-			var node;
-			if (sublist.length > 1) {
-				node = wrap(sublist,
-					function() { return false },
-					function() { return document.createElement("li") });
-
-			// "Otherwise, let node be the sole member of sublist."
-			} else {
-				node = sublist[0];
-			}
-
-			// "If node is an HTML element with local name other tag name:"
-			if (isHtmlElement(node, otherTagName)) {
-				// "Let children be the children of node."
-				var children = [].slice.call(node.childNodes);
-
-				// "Remove node, preserving its descendants."
-				removePreservingDescendants(node);
-
-				// "Wrap children, with sibling criteria matching any HTML
-				// element with local name tag name and new parent instructions
-				// returning the result of calling createElement(tag name) on
-				// the context object. Let node be the result."
-				node = wrap(children,
-					function(node) { return isHtmlElement(node, tagName) },
-					function() { return document.createElement(tagName) });
-
-				// "Prepend the descendants of node that are HTML elements with
-				// local name other tag name (if any) to node list."
-				nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
-
-				// "Continue from the beginning of this loop."
-				continue;
-			}
-
-			// "If node is a p or div, set the tag name of node to "li",
-			// and let node be the result."
-			if (isHtmlElement(node, ["P", "DIV"])) {
-				node = setTagName(node, "li");
-			}
-
-			// "If node is the child of an HTML element with local name other
-			// tag name:"
-			if (isHtmlElement(node.parentNode, otherTagName)) {
-				// "Split the parent of the one-node list consisting of
-				// node."
-				splitParent([node]);
-
-				// "Wrap the one-node list consisting of node, with sibling
-				// criteria matching any HTML element with local name tag name,
-				// and with new parent instructions returning the result of
-				// calling createElement(tag name) on the context object."
-				wrap([node],
-					function(node) { return isHtmlElement(node, tagName) },
-					function() { return document.createElement(tagName) });
-
-				// "Prepend the descendants of node that are HTML elements with
-				// local name other tag name (if any) to node list."
-				nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
-
-				// "Continue from the beginning of this loop."
-				continue;
-			}
-
-			// "If node is equal to or the child of an HTML element with local
-			// name tag name, prepend the descendants of node that are HTML
-			// elements with local name other tag name (if any) to node list
-			// and continue from the beginning of this loop."
-			if (isHtmlElement(node, tagName)
-			|| isHtmlElement(node.parentNode, tagName)) {
-				nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
-				continue;
-			}
-
-			// "If node is not an li, wrap the one-node list consisting of
-			// node, with sibling criteria matching nothing and new parent
-			// instructions returning the result of calling createElement("li")
-			// on the context object. Set node to the result."
-			if (!isHtmlElement(node, "LI")) {
-				node = wrap([node],
-					function() { return false },
-					function() { return document.createElement("li") });
-			}
-
-			// "Wrap the one-node list consisting of node, with the sibling
-			// criteria matching any HTML element with local name tag name, and
-			// the new parent instructions being the following:"
-			var newParent = wrap([node],
-				function(node) { return isHtmlElement(node, tagName) },
-				function() {
-					// "If the parent of node is not an editable indentation
-					// element, or the previousSibling of the parent of node is
-					// not an editable HTML element with local name tag name,
-					// call createElement(tag name) on the context object and
-					// return the result. Otherwise:"
-					if (!isEditable(node.parentNode)
-					|| !isIndentationElement(node.parentNode)
-					|| !isEditable(node.parentNode.previousSibling)
-					|| !isHtmlElement(node.parentNode.previousSibling, tagName)) {
-						return document.createElement(tagName);
-					}
-
-					// "Let list be the previousSibling of the parent of node."
-					var list = node.parentNode.previousSibling;
-
-					// "Normalize sublists of list's last child."
-					normalizeSublists(list.lastChild);
-
-					// "If list's last child is not an editable HTML element
-					// with local name tag name, call createElement(tag name)
-					// on the context object, and append the result as the last
-					// child of list."
-					if (!isEditable(list.lastChild)
-					|| !isHtmlElement(list.lastChild, tagName)) {
-						list.appendChild(document.createElement(tagName));
-					}
-
-					// "Return the last child of list."
-					return list.lastChild;
-				});
-
-			// "Fix disallowed ancestors of the previous step's result."
-			fixDisallowedAncestors(newParent);
-		}
-	}
-}
-
-function justifySelection(alignment) {
-	// "Block-extend the active range, and let new range be the result."
-	var newRange = blockExtendRange(globalRange);
-
-	// "Let element list be a list of all editable Elements contained in new
-	// range that either has an attribute in the HTML namespace whose local
-	// name is "align", or has a style attribute that sets "text-align", or is
-	// a center."
-	var elementList = collectAllContainedNodes(newRange, function(node) {
-		return node.nodeType == Node.ELEMENT_NODE
-			&& isEditable(node)
-			// Ignoring namespaces here
-			&& (
-				node.hasAttribute("align")
-				|| node.style.textAlign != ""
-				|| isHtmlElement(node, "center")
-			);
-	});
-
-	// "For each element in element list:"
-	for (var i = 0; i < elementList.length; i++) {
-		var element = elementList[i];
-
-		// "If element has an attribute in the HTML namespace whose local name
-		// is "align", remove that attribute."
-		element.removeAttribute("align");
-
-		// "Unset the CSS property "text-align" on element, if it's set by a
-		// style attribute."
-		element.style.textAlign = "";
-		if (element.getAttribute("style") == "") {
-			element.removeAttribute("style");
-		}
-
-		// "If element is a div or center with no attributes, remove it,
-		// preserving its descendants."
-		if (isHtmlElement(element, ["div", "center"])
-		&& !element.attributes.length) {
-			removePreservingDescendants(element);
-		}
-
-		// "If element is a center with one or more attributes, set the tag
-		// name of element to "div"."
-		if (isHtmlElement(element, "center")
-		&& element.attributes.length) {
-			setTagName(element, "div");
-		}
-	}
-
-	// "Block-extend the active range, and let new range be the result."
-	newRange = blockExtendRange(globalRange);
-
-	// "Let node list be a list of nodes, initially empty."
-	var nodeList = [];
-
-	// "For each node node contained in new range, append node to node list if
-	// the last member of node list (if any) is not an ancestor of node; node
-	// is editable; and either node is an Element and the CSS property
-	// "text-align" does not compute to alignment on it, or it is not an
-	// Element, but its parent is an Element, and the CSS property "text-align"
-	// does not compute to alignment on its parent."
-	nodeList = collectContainedNodes(newRange, function(node) {
-		if (!isEditable(node)) {
-			return false;
-		}
-		// Gecko and WebKit have lots of fun here confusing us with
-		// vendor-specific values, and in Gecko's case "start".
-		var element = node.nodeType == Node.ELEMENT_NODE
-			? node
-			: node.parentNode;
-		if (!element || element.nodeType != Node.ELEMENT_NODE) {
-			return false;
-		}
-		var computedAlign = getComputedStyle(element).textAlign
-			.replace(/^-(moz|webkit)-/, "");
-		if (computedAlign == "auto" || computedAlign == "start") {
-			// Depends on directionality.  Note: this is a serious hack.
-			do {
-				var dir = element.dir.toLowerCase();
-				element = element.parentNode;
-			} while (element && element.nodeType == Node.ELEMENT_NODE && dir != "ltr" && dir != "rtl");
-			if (dir == "rtl") {
-				computedAlign = "right";
-			} else {
-				computedAlign = "left";
-			}
-		}
-		return computedAlign != alignment;
-	});
-
-	// "While node list is not empty:"
-	while (nodeList.length) {
-		// "Let sublist be a list of nodes, initially empty."
-		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, remove the first
-		// member of node list and append it to sublist."
-		while (nodeList.length
-		&& nodeList[0] == sublist[sublist.length - 1].nextSibling) {
-			sublist.push(nodeList.shift());
-		}
-
-		// "Wrap sublist. Sibling criteria match any div that has one or both
-		// of the following two attributes, and no other attributes:
-		//
-		//   * "An align attribute whose value is an ASCII case-insensitive
-		//     match for alignment.
-		//   * "A style attribute which sets exactly one CSS property
-		//     (including unrecognized or invalid attributes), which is
-		//     "text-align", which is set to alignment.
-		//
-		// "New parent instructions are to call createElement("div") on the
-		// context object, then set its CSS property "text-align" to alignment,
-		// and return the result."
-		wrap(sublist,
-			function(node) {
-				return isHtmlElement(node, "div")
-					&& [].every.call(node.attributes, function(attr) {
-						return (attr.name == "align" && attr.value.toLowerCase() == alignment)
-							|| (attr.name == "style" && node.style.length == 1 && node.style.textAlign == alignment);
-					});
-			},
-			function() {
-				var newParent = document.createElement("div");
-				newParent.setAttribute("style", "text-align: " + alignment);
-				return newParent;
-			}
-		);
-	}
-}
-
-// "A potential indentation element is either a blockquote, or a div that has a
-// style attribute that sets "margin" or some subproperty of it."
-function isPotentialIndentationElement(node) {
-	if (!isHtmlElement(node)) {
-		return false;
-	}
-
-	if (node.tagName == "BLOCKQUOTE") {
-		return true;
-	}
-
-	if (node.tagName != "DIV") {
-		return false;
-	}
-
-	for (var i = 0; i < node.style.length; i++) {
-		// Approximate check
-		if (/^(-[a-z]+-)?margin/.test(node.style[i])) {
-			return true;
-		}
-	}
-
-	return false;
-}
-
-// "An indentation element is a potential indentation element that has no
-// attributes other than one or more of
-//
-//   * "a style attribute that sets no properties other than "margin", "border",
-//     "padding", or subproperties of those;
-//   * "a class attribute;
-//   * "a dir attribute."
-function isIndentationElement(node) {
-	if (!isPotentialIndentationElement(node)) {
-		return false;
-	}
-
-	if (node.tagName != "BLOCKQUOTE" && node.tagName != "DIV") {
-		return false;
-	}
-
-	for (var i = 0; i < node.attributes.length; i++) {
-		if (!isHtmlNamespace(node.attributes[i].namespaceURI)
-		|| ["style", "class", "dir"].indexOf(node.attributes[i].name) == -1) {
-			return false;
-		}
-	}
-
-	for (var i = 0; i < node.style.length; i++) {
-		// This is approximate, but it works well enough for my purposes.
-		if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {
-			return false;
-		}
-	}
-
-	return true;
-}
-
-// "A non-list single-line container is an HTML element with local name
-// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre"."
-function isNonListSingleLineContainer(node) {
-	return isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5",
-		"h6", "p", "pre"]);
-}
-
-// "A single-line container is either a non-list single-line container, or an
-// HTML element with local name "li", "dt", or "dd"."
-function isSingleLineContainer(node) {
-	return isNonListSingleLineContainer(node)
-		|| isHtmlElement(node, ["li", "dt", "dd"]);
-}
-
 function myQueryCommandState(command) {
 	command = command.toLowerCase();
 
@@ -5270,3 +5379,5 @@
 
 	return "";
 }
+
+// vim: [email protected]{,@} foldmethod=marker
--- a/source.html	Tue Jun 14 13:46:20 2011 -0600
+++ b/source.html	Tue Jun 14 14:37:27 2011 -0600
@@ -355,6 +355,80 @@
 things.  If it is, it needs some adjustment, like to handle collapsed
 whitespace nodes and collapsed br's.
 
+<!-- I don't remember why I wrote this.  Keeping it around just in case it
+turns out to be useful.
+
+<p><var>node</var> is an <dfn>extraneous line break</dfn> if the following
+algorithm returns true:
+
+<p class=XXX>This is positively horrible.  It attempts to move complicated CSS
+logic into DOM methods, and does so badly.  Can we somehow make this less evil,
+preferably much less evil?
+
+<ol>
+  <li>If <var>node</var> is not a [[br]], return false.
+
+  <li>Let <var>ancestor block</var> be <var>node</var>.
+
+  <li>While <var>ancestor block</var> is an <span>inline node</span>, set
+  <var>ancestor block</var> to its [[parent]].
+
+  <li>Let <var>previous box</var> be <var>node</var>.
+
+  <li>While <var>previous box</var> is equal to or an [[ancestor]] of
+  <var>node</var>, set <var>previous box</var> to the [[node]] immediately
+  before it in [[treeorder]], or null if there is no such [[node]].
+
+  <li>Let <var>next box</var> be the [[node]] immediately after <var>node</var>
+  in [[treeorder]], or null if there is such [[node]].
+
+  <li>If <var>previous box</var> is null, or is not a [[descendant]] of
+  <var>ancestor block</var>, or is not an <span>inline node</span>, or is a
+  [[br]], return false.
+  <!- -
+  This means br either is the first thing in its block container that generates
+  an inline box, or it's the first thing after another block container, or the
+  first thing after a br.  In any case, the line break will be visible.
+
+  Otherwise, it will be invisible as long as it immediately precedes a block
+  box boundary.
+  - ->
+
+  <li>If <var>ancestor block</var> is not null and <var>node</var> is the last
+  [[descendant]] of <var>ancestor block</var>, return true.
+  <!- - Precedes the end of ancestor block's box. - ->
+
+  <li>If <var>next box</var> is not null and not an <span>inline node</span>,
+  return true.
+  <!- - Precedes the start of next box's box. - ->
+
+  <li>Return false.
+</ol>
+-->
+
+<p>Each [[htmldocument]] has a boolean <dfn>CSS styling flag</dfn> associated
+with it, which must initially be false.  (<span>The <code
+title>styleWithCSS</code> command</span> can be used to modify or query it, by
+means of the <code>execCommand()</code> and <code>queryCommandState()</code>
+methods.)
+
+<p>When the user agent is instructed to run a particular method, it must follow
+the steps defined for that method in the appropriate specification, not act as
+though the method had actually been called from JavaScript.  In particular,
+if the author has overridden the method with a custom method, the standard
+method must be run rather than the custom one.
+
+<p>When a list or set of [[nodes]] is assigned to a variable without specifying
+the order, they must be initially in [[treeorder]], if they share a root.
+(If they don't share a root, the order will be specified.)  When the user agent
+is instructed to run particular steps for each member of a list, it must do so
+sequentially in the list's order.
+
+
+<h2>Common algorithms</h2>
+
+<h3>Assorted common algorithms</h3>
+
 <p>A [[node]] or string <var>child</var> is an <dfn>allowed child</dfn> of a
 [[node]] or string <var>parent</var> if the following algorithm returns true:
 
@@ -478,79 +552,34 @@
   <li>Return true.
 </ol>
 
-<!-- I don't remember why I wrote this.  Keeping it around just in case it
-turns out to be useful.
-
-<p><var>node</var> is an <dfn>extraneous line break</dfn> if the following
-algorithm returns true:
-
-<p class=XXX>This is positively horrible.  It attempts to move complicated CSS
-logic into DOM methods, and does so badly.  Can we somehow make this less evil,
-preferably much less evil?
+<p>To move a [[node]] to a new location, <dfn>preserving ranges</dfn>, remove
+the [[node]] from its original [[parent]] (if any), then insert it in the new
+location.  In doing so, however, ignore the regular [[rangemutationrules]], and
+instead follow these rules:
 
 <ol>
-  <li>If <var>node</var> is not a [[br]], return false.
-
-  <li>Let <var>ancestor block</var> be <var>node</var>.
-
-  <li>While <var>ancestor block</var> is an <span>inline node</span>, set
-  <var>ancestor block</var> to its [[parent]].
-
-  <li>Let <var>previous box</var> be <var>node</var>.
-
-  <li>While <var>previous box</var> is equal to or an [[ancestor]] of
-  <var>node</var>, set <var>previous box</var> to the [[node]] immediately
-  before it in [[treeorder]], or null if there is no such [[node]].
-
-  <li>Let <var>next box</var> be the [[node]] immediately after <var>node</var>
-  in [[treeorder]], or null if there is such [[node]].
-
-  <li>If <var>previous box</var> is null, or is not a [[descendant]] of
-  <var>ancestor block</var>, or is not an <span>inline node</span>, or is a
-  [[br]], return false.
-  <!- -
-  This means br either is the first thing in its block container that generates
-  an inline box, or it's the first thing after another block container, or the
-  first thing after a br.  In any case, the line break will be visible.
-
-  Otherwise, it will be invisible as long as it immediately precedes a block
-  box boundary.
-  - ->
-
-  <li>If <var>ancestor block</var> is not null and <var>node</var> is the last
-  [[descendant]] of <var>ancestor block</var>, return true.
-  <!- - Precedes the end of ancestor block's box. - ->
-
-  <li>If <var>next box</var> is not null and not an <span>inline node</span>,
-  return true.
-  <!- - Precedes the start of next box's box. - ->
-
-  <li>Return false.
+  <li>Let <var>node</var> be the moved [[node]], <var>old parent</var> and
+  <var>old index</var> be the old [[parent]] (which may be null) and [[index]],
+  and <var>new parent</var> and <var>new index</var> be the new [[parent]] and
+  [[index]].
+
+  <li>If a [[boundarypoint]]'s [[bpnode]] is the same as or a [[descendant]] of
+  <var>node</var>, leave it unchanged, so it moves to the new location.  <!--
+  This is actually implicit, but I state it anyway for completeness. -->
+
+  <li>If a [[boundarypoint]]'s [[bpnode]] is <var>new parent</var> and its
+  [[bpoffset]] is greater than <var>new index</var>, add one to its
+  [[bpoffset]].
+
+  <li>If a [[boundarypoint]]'s [[bpnode]] is <var>old parent</var> and its
+  [[bpoffset]] is <var>old index</var> or <var>old index</var> + 1, set its
+  [[bpnode]] to <var>new parent</var> and add <var>new index</var> &minus;
+  <var>old index</var> to its [[bpoffset]].
+
+  <li>If a [[boundarypoint]]'s [[bpnode]] is <var>old parent</var> and its
+  [[bpoffset]] is greater than <var>old index</var> + 1, subtract one from its
+  [[bpoffset]].
 </ol>
--->
-
-<p>Each [[htmldocument]] has a boolean <dfn>CSS styling flag</dfn> associated
-with it, which must initially be false.  (<span>The <code
-title>styleWithCSS</code> command</span> can be used to modify or query it, by
-means of the <code>execCommand()</code> and <code>queryCommandState()</code>
-methods.)
-
-<p>When the user agent is instructed to run a particular method, it must follow
-the steps defined for that method in the appropriate specification, not act as
-though the method had actually been called from JavaScript.  In particular,
-if the author has overridden the method with a custom method, the standard
-method must be run rather than the custom one.
-
-<p>When a list or set of [[nodes]] is assigned to a variable without specifying
-the order, they must be initially in [[treeorder]], if they share a root.
-(If they don't share a root, the order will be specified.)  When the user agent
-is instructed to run particular steps for each member of a list, it must do so
-sequentially in the list's order.
-
-
-<h2>Common algorithms</h2>
-
-<h3>Assorted common algorithms</h3>
 
 <p>To <dfn>set the tag name</dfn> of an [[element]] <var>element</var> to
 <var>new name</var>:
@@ -736,35 +765,6 @@
 descendants</dfn>, <span>split the parent</span> of <var>node</var>'s
 [[children]].
 
-<p>To move a [[node]] to a new location, <dfn>preserving ranges</dfn>, remove
-the [[node]] from its original [[parent]] (if any), then insert it in the new
-location.  In doing so, however, ignore the regular [[rangemutationrules]], and
-instead follow these rules:
-
-<ol>
-  <li>Let <var>node</var> be the moved [[node]], <var>old parent</var> and
-  <var>old index</var> be the old [[parent]] (which may be null) and [[index]],
-  and <var>new parent</var> and <var>new index</var> be the new [[parent]] and
-  [[index]].
-
-  <li>If a [[boundarypoint]]'s [[bpnode]] is the same as or a [[descendant]] of
-  <var>node</var>, leave it unchanged, so it moves to the new location.  <!--
-  This is actually implicit, but I state it anyway for completeness. -->
-
-  <li>If a [[boundarypoint]]'s [[bpnode]] is <var>new parent</var> and its
-  [[bpoffset]] is greater than <var>new index</var>, add one to its
-  [[bpoffset]].
-
-  <li>If a [[boundarypoint]]'s [[bpnode]] is <var>old parent</var> and its
-  [[bpoffset]] is <var>old index</var> or <var>old index</var> + 1, set its
-  [[bpnode]] to <var>new parent</var> and add <var>new index</var> &minus;
-  <var>old index</var> to its [[bpoffset]].
-
-  <li>If a [[boundarypoint]]'s [[bpnode]] is <var>old parent</var> and its
-  [[bpoffset]] is greater than <var>old index</var> + 1, subtract one from its
-  [[bpoffset]].
-</ol>
-
 
 <h3>Wrapping a list of nodes</h3>
 
@@ -1366,6 +1366,9 @@
 display property computes to something other than "inline", "inline-block", or
 "inline-table"; or any [[node]] that is not <span>editable</span>.
 
+<p class=XXX>Probably want to redefine unwrappable node in terms of allowed
+children or something.
+
 <p>A <dfn>modifiable element</dfn> is a [[b]], [[em]], [[i]], [[s]], [[span]],
 [[strike]], [[strong]], [[sub]], [[sup]], or [[u]] element with no attributes
 except possibly [[style]]; or a [[font]] element with no attributes except
@@ -2453,282 +2456,6 @@
 <!-- I'd have expected the value to be the URL, but guess not. -->
 
 
-<h3><dfn>The <code title>delete</code> command</dfn></h3>
-<!-- Not really specifically "inline", but I didn't want to create a new
-section just for it. -->
-
-<p><span>Action</span>:
-
-<ol>
-  <li>If the <span>active range</span> is not <code data-anolis-spec=domrange
-  title=dom-Range-collapsed>collapsed</code>, <span>delete the contents</span>
-  of the <span>active range</span> and abort these steps.
-
-  <p class=XXX>Maybe we sometimes want to do the following even if it isn't
-  collapsed?  WebKit seems to do some normalization on the range before
-  deciding whether it's collapsed, and that sounds like a good idea.
-
-  <li>Let <var>node</var> and <var>offset</var> be the <span>active
-  range</span>'s [[rangestart]] [[bpnode]] and [[bpoffset]].
-
-  <!-- First go up as high as possible within the current block, then drill
-  down to the lowest possible level, in the hopes that we'll wind up at the end
-  of a text node, or maybe in a br or hr. -->
-  <li>Repeat the following steps:
-
-  <ol>
-    <!--
-    If there's an invisible node somewhere, Firefox 5.0a2 removes that node and
-    then stops, so each backspace removes one invisible node.  All others
-    remove the invisible node and then continue on looking for something
-    visible to remove.  The spec follows the latter behavior, since it makes
-    more sense to the user.  Of course, the definition of "invisible node" is
-    not necessarily anything like the spec's.
-    -->
-    <li>If <var>offset</var> is zero and <var>node</var>'s [[previoussibling]]
-    is an <span>editable</span> <span>invisible node</span>, remove
-    <var>node</var>'s [[previoussibling]] from its [[parent]].
-
-    <li>Otherwise, if <var>node</var> has a [[child]] with [[index]]
-    <var>offset</var> &minus; 1 and that [[child]] is an <span>editable</span>
-    <span>invisible node</span>, remove that [[child]] from <var>node</var>,
-    then subtract one from <var>offset</var>.
-
-    <li>Otherwise, if <var>offset</var> is zero and <var>node</var> is not a
-    <span>prohibited paragraph child</span>, or if <var>node</var> is an
-    <span>invisible node</span>, set <var>offset</var> to the [[index]] of
-    <var>node</var>, then set <var>node</var> to its [[parent]].
-
-    <li>Otherwise, if <var>node</var> has a [[child]] with [[index]]
-    <var>offset</var> &minus; 1 and that [[child]] is not a <span>prohibited
-    paragraph child</span> or a [[br]] or an [[img]], set <var>node</var> to
-    that [[child]], then set <var>offset</var> to the [[nodelength]] of
-    <var>node</var>.
-
-    <li>Otherwise, break from this loop.
-  </ol>
-
-  <!--
-  At this point, node cannot be an invisible node.  There are three cases:
-
-  1) offset is zero and node is a prohibited paragraph child.  Then we'll
-  usually merge with the previous block if one exists.
-
-  2) offset is not zero, node is not a prohibited paragraph child, and node
-  does not have a child with index offset - 1.  The only way this is possible
-  is if node has a length greater than zero but no children, which implies it's
-  a text or comment or PI.  Comments and PIs are invisible nodes, so it must be
-  a text node.  We delete the previous character.
-
-  3) offset is not zero, and the child of node with index offset - 1 is a
-  prohibited paragraph child or a br or an img.  Then we'll usually merge the
-  offsetth child of node with the last descendant of the offset - 1st.
-  -->
-
-  <li>If <var>node</var> is a [[text]] node and <var>offset</var> is not zero,
-  call [[selcollapse|<var>node</var>, <var>offset</var>]] on the [[selection]].
-  Then <span>delete the contents</span> of the [[range]] with [[rangestart]]
-  (<var>node</var>, <var>offset</var> &minus; 1) and [[rangeend]]
-  (<var>node</var>, <var>offset</var>) and abort these steps.
-
-  <!-- At the time of this writing, this should be impossible. -->
-  <li>If <var>node</var> is not a <span>prohibited paragraph child</span>,
-  abort these steps.
-
-  <li>If <var>node</var> has a [[child]] with [[index]] <var>offset</var>
-  &minus; 1 and that [[child]] is a [[br]] or [[hr]] or [[img]], call
-  [[selcollapse|<var>node</var>, <var>offset</var>]] on the [[selection]].
-  Then <span>delete the contents</span> of the [[range]] with [[rangestart]]
-  (<var>node</var>, <var>offset</var> &minus; 1) and [[rangeend]]
-  (<var>node</var>, <var>offset</var>) and abort these steps.
-
-  <!--
-  If we're at the beginning of a list, we want to outdent the first list item.
-  This doesn't actually match anyone or anything.  Word 2007 and OpenOffice.org
-  3.2.1 Ubuntu just remove the list marker, which is weird and doesn't map well
-  to HTML.  Browsers tend to just merge with the preceding block, which isn't
-  expected.
-  -->
-  <li>If <var>node</var> is an [[li]] or [[dt]] or [[dd]] and is the first
-  [[child]] of its [[parent]]:
-
-  <ol>
-    <li>Let <var>items</var> be a list of all [[li]]s that are
-    [[ancestors]] of <var>node</var>.
-
-    <li><span>Normalize sublists</span> of each <var>item</var> in
-    <var>items</var>.
-
-    <li><span>Split the parent</span> of the one-[[node]] list consisting of
-    <var>node</var>.
-
-    <li><span>Fix disallowed ancestors</span> of <var>node</var>.
-
-    <li>Abort these steps.
-  </ol>
-
-  <!-- By this point, we're almost certainly going to merge something, and the
-  only question is what. -->
-  <li>Let <var>start node</var> equal <var>node</var> and let <var>start
-  offset</var> equal <var>offset</var>.
-
-  <li>While <var>start offset</var> is zero, set <var>start offset</var> to the
-  [[index]] of <var>start node</var> and then set <var>start node</var> to its
-  [[parent]].
-
-  <p class=XXX>The node at index start offset &minus; 1 might be an invisible
-  node.
-
-  <!--
-  At the beginning of an indented block, outdent it, similar to a list item.
-  Browsers don't do this, word processors do.
-  -->
-  <li>If <var>offset</var> is zero, and <var>node</var> has an
-  [[ancestorcontainer]] that is both a <span>potential indentation
-  element</span> and a [[descendant]] of <var>start node</var>:
-
-  <p class=XXX>This copy-pastes from the outdent command action.  I'm also not
-  totally sure it's correct.
-
-  <ol>
-    <li><span>Block-extend</span> the [[range]] whose [[rangestart]] and
-    [[rangeend]] are both (<var>node</var>, 0), and let <var>new range</var> be
-    the result.
-
-    <li>Let <var>node list</var> be a list of [[nodes]], initially empty.
-
-    <li>For each [[node]] <var>current node</var> [[contained]] in <var>new
-    range</var>, append <var>current node</var> to <var>node list</var> if the
-    last member of <var>node list</var> (if any) is not an [[ancestor]] of
-    <var>current node</var>, and <var>current node</var> is
-    <span>editable</span> but has no <span>editable</span> [[descendants]].
-
-    <li><span>Outdent</span> each [[node]] in <var>node list</var>.
-
-    <li>Abort these steps.
-  </ol>
-
-  <!--
-  This is to avoid stripping a line break from
-
-    foo<br><br><table><tr><td>[]bar</table>
-
-  and similarly for <hr>.  We should just do nothing here.
-  -->
-  <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
-  offset</var> is a [[table]], abort these steps.
-
-  <!--
-  If you try backspacing into a table, select it.  This doesn't match any
-  browser; it matches the recommendation of the "behavior when typing in
-  contentEditable elements" document.  The idea is that then you can delete it
-  with a second backspace.
-  -->
-  <li>If <var>start node</var> has a [[child]] with [[index]] <var>start
-  offset</var> &minus; 1, and that [[child]] is a [[table]]:
-
-  <ol>
-    <li>Call [[selcollapse|<var>start node</var>, <var>start offset</var>
-    &minus; 1]] on the [[contextobject]]'s [[selection]].
-
-    <li>Call [[extend|<var>start node</var>, <var>start offset</var>]] on the
-    [[contextobject]]'s [[selection]].
-
-    <li>Abort these steps.
-  </ol>
-
-  <!--
-  Special case:
-
-    <p>foo</p><br><p>[]bar</p>
-    -> <p>foo</p><p>[]bar</p>
-
-  and likewise for <hr>.  But with <img> we merge like in other cases:
-
-    <p>foo</p><img><p>[]bar</p>
-    -> <p>foo</p><img>[]bar.
-
-  Browsers don't do this consistently.  Firefox 5.0a2 doesn't seem to do it at
-  all.
-  -->
-  <li>If <var>offset</var> is zero; and either the [[child]] of <var>start
-  node</var> with [[index]] <var>start offset</var> minus one is an [[hr]], or
-  the [[child]] is a [[br]] whose [[previoussibling]] is either a [[br]] or not
-  an <span>inline node</span>:
-
-  <ol>
-    <li>Call [[selcollapse|<var>node</var>, <var>offset</var>]] on the
-    [[selection]].
-
-    <li><span>Delete the contents</span> of the [[range]] with [[rangestart]]
-    (<var>start node</var>, <var>start offset</var> &minus; 1) and [[rangeend]]
-    (<var>start node</var>, <var>start offset</var>).
-
-    <li>Abort these steps.
-  </ol>
-
-  <!--
-  If you try backspacing out of a list item, merge it with the previous item,
-  but add a line break.  Then you have to backspace again if you really want
-  them to be on the same line.  This matches Word 2007 and OpenOffice.org 3.2.1
-  Ubuntu, and also matches "behavior when typing in contentEditable elements",
-  but does not match any browser.
-
-  Note that this behavior is quite different from what happens if you actually
-  select the linebreak in between the two lines.  In that case, the blocks are
-  merged as normal.
-
-  Also note that hitting backspace twice will merge with the previous item.
-  This matches OO.org, but Word will outdent the item on subsequent backspaces.
-  Word's behavior doesn't fit well with the way lists work in HTML, and we
-  probably don't want it.
-  -->
-  <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
-  offset</var> is an [[li]] or [[dt]] or [[dd]], and that [[child]]'s
-  [[firstchild]] is an <span>inline node</span>, and <var>start offset</var> is
-  not zero:
-
-  <ol>
-    <li>Let <var>previous item</var> be the [[child]] of <var>start node</var>
-    with [[index]] <var>start offset</var> minus one.
-
-    <!-- If the last child is already a br, we only need to append one extra
-    br.  Otherwise we need to append two, since the first will do nothing. -->
-    <li>If <var>previous item</var>'s [[lastchild]] is an <span>inline
-    node</span> other than a [[br]], call [[createelement|"br"]] on the
-    [[contextobject]] and append the result as the last [[child]] of
-    <var>previous item</var>.
-
-    <li>If <var>previous item</var>'s [[lastchild]] is an <span>inline
-    node</span>, call [[createelement|"br"]] on the [[contextobject]] and
-    append the result as the last [[child]] of <var>previous item</var>.
-  </ol>
-
-  <!--
-  When merging adjacent list items, make sure we only merge the items
-  themselves, not any block children.  We want <li><p>foo<li><p>bar to become
-  <li><p>foo<p>bar, not <li><p>foo<br>bar or <li><p>foobar.
-  -->
-  <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
-  offset</var> is an [[li]] or [[dt]] or [[dd]], and its [[previoussibling]] is
-  also an [[li]] or [[dt]] or [[dd]], set <var>start node</var> to its
-  [[child]] with [[index]] <var>start offset</var> &minus; 1, then set
-  <var>start offset</var> to <var>start node</var>'s [[nodelength]], then set
-  <var>node</var> to <var>start node</var>'s [[nextsibling]], then set
-  <var>offset</var> to 0.
-
-  <!-- General block-merging case. -->
-  <li>Otherwise, while <var>start node</var> has a [[child]] with [[index]]
-  <var>start offset</var> minus one, set <var>start node</var> to that
-  [[child]], then set <var>start offset</var> to the [[nodelength]] of
-  <var>start node</var>.
-
-  <li><span>Delete the contents</span> of the [[range]] with [[rangestart]]
-  (<var>start node</var>, <var>start offset</var>) and [[rangeend]]
-  (<var>node</var>, <var>offset</var>).
-</ol>
-
-
 <h3><dfn>The <code title>fontName</code> command</dfn></h3>
 
 <p><span>Action</span>: <span>Decompose</span> the <span>active range</span>,
@@ -3740,6 +3467,9 @@
 
 <h3>Block-formatting a node list</h3>
 
+<p class=XXX>Why is this a separate section?  There's only one caller.
+Probably want to merge it back.
+
 <p>To <dfn>block-format</dfn> a list of [[nodes]] <var>input nodes</var> to a
 string <var>value</var>:
 
@@ -4728,6 +4458,280 @@
 </ol>
 
 
+<h3><dfn>The <code title>delete</code> command</dfn></h3>
+
+<p><span>Action</span>:
+
+<ol>
+  <li>If the <span>active range</span> is not <code data-anolis-spec=domrange
+  title=dom-Range-collapsed>collapsed</code>, <span>delete the contents</span>
+  of the <span>active range</span> and abort these steps.
+
+  <p class=XXX>Maybe we sometimes want to do the following even if it isn't
+  collapsed?  WebKit seems to do some normalization on the range before
+  deciding whether it's collapsed, and that sounds like a good idea.
+
+  <li>Let <var>node</var> and <var>offset</var> be the <span>active
+  range</span>'s [[rangestart]] [[bpnode]] and [[bpoffset]].
+
+  <!-- First go up as high as possible within the current block, then drill
+  down to the lowest possible level, in the hopes that we'll wind up at the end
+  of a text node, or maybe in a br or hr. -->
+  <li>Repeat the following steps:
+
+  <ol>
+    <!--
+    If there's an invisible node somewhere, Firefox 5.0a2 removes that node and
+    then stops, so each backspace removes one invisible node.  All others
+    remove the invisible node and then continue on looking for something
+    visible to remove.  The spec follows the latter behavior, since it makes
+    more sense to the user.  Of course, the definition of "invisible node" is
+    not necessarily anything like the spec's.
+    -->
+    <li>If <var>offset</var> is zero and <var>node</var>'s [[previoussibling]]
+    is an <span>editable</span> <span>invisible node</span>, remove
+    <var>node</var>'s [[previoussibling]] from its [[parent]].
+
+    <li>Otherwise, if <var>node</var> has a [[child]] with [[index]]
+    <var>offset</var> &minus; 1 and that [[child]] is an <span>editable</span>
+    <span>invisible node</span>, remove that [[child]] from <var>node</var>,
+    then subtract one from <var>offset</var>.
+
+    <li>Otherwise, if <var>offset</var> is zero and <var>node</var> is not a
+    <span>prohibited paragraph child</span>, or if <var>node</var> is an
+    <span>invisible node</span>, set <var>offset</var> to the [[index]] of
+    <var>node</var>, then set <var>node</var> to its [[parent]].
+
+    <li>Otherwise, if <var>node</var> has a [[child]] with [[index]]
+    <var>offset</var> &minus; 1 and that [[child]] is not a <span>prohibited
+    paragraph child</span> or a [[br]] or an [[img]], set <var>node</var> to
+    that [[child]], then set <var>offset</var> to the [[nodelength]] of
+    <var>node</var>.
+
+    <li>Otherwise, break from this loop.
+  </ol>
+
+  <!--
+  At this point, node cannot be an invisible node.  There are three cases:
+
+  1) offset is zero and node is a prohibited paragraph child.  Then we'll
+  usually merge with the previous block if one exists.
+
+  2) offset is not zero, node is not a prohibited paragraph child, and node
+  does not have a child with index offset - 1.  The only way this is possible
+  is if node has a length greater than zero but no children, which implies it's
+  a text or comment or PI.  Comments and PIs are invisible nodes, so it must be
+  a text node.  We delete the previous character.
+
+  3) offset is not zero, and the child of node with index offset - 1 is a
+  prohibited paragraph child or a br or an img.  Then we'll usually merge the
+  offsetth child of node with the last descendant of the offset - 1st.
+  -->
+
+  <li>If <var>node</var> is a [[text]] node and <var>offset</var> is not zero,
+  call [[selcollapse|<var>node</var>, <var>offset</var>]] on the [[selection]].
+  Then <span>delete the contents</span> of the [[range]] with [[rangestart]]
+  (<var>node</var>, <var>offset</var> &minus; 1) and [[rangeend]]
+  (<var>node</var>, <var>offset</var>) and abort these steps.
+
+  <!-- At the time of this writing, this should be impossible. -->
+  <li>If <var>node</var> is not a <span>prohibited paragraph child</span>,
+  abort these steps.
+
+  <li>If <var>node</var> has a [[child]] with [[index]] <var>offset</var>
+  &minus; 1 and that [[child]] is a [[br]] or [[hr]] or [[img]], call
+  [[selcollapse|<var>node</var>, <var>offset</var>]] on the [[selection]].
+  Then <span>delete the contents</span> of the [[range]] with [[rangestart]]
+  (<var>node</var>, <var>offset</var> &minus; 1) and [[rangeend]]
+  (<var>node</var>, <var>offset</var>) and abort these steps.
+
+  <!--
+  If we're at the beginning of a list, we want to outdent the first list item.
+  This doesn't actually match anyone or anything.  Word 2007 and OpenOffice.org
+  3.2.1 Ubuntu just remove the list marker, which is weird and doesn't map well
+  to HTML.  Browsers tend to just merge with the preceding block, which isn't
+  expected.
+  -->
+  <li>If <var>node</var> is an [[li]] or [[dt]] or [[dd]] and is the first
+  [[child]] of its [[parent]]:
+
+  <ol>
+    <li>Let <var>items</var> be a list of all [[li]]s that are
+    [[ancestors]] of <var>node</var>.
+
+    <li><span>Normalize sublists</span> of each <var>item</var> in
+    <var>items</var>.
+
+    <li><span>Split the parent</span> of the one-[[node]] list consisting of
+    <var>node</var>.
+
+    <li><span>Fix disallowed ancestors</span> of <var>node</var>.
+
+    <li>Abort these steps.
+  </ol>
+
+  <!-- By this point, we're almost certainly going to merge something, and the
+  only question is what. -->
+  <li>Let <var>start node</var> equal <var>node</var> and let <var>start
+  offset</var> equal <var>offset</var>.
+
+  <li>While <var>start offset</var> is zero, set <var>start offset</var> to the
+  [[index]] of <var>start node</var> and then set <var>start node</var> to its
+  [[parent]].
+
+  <p class=XXX>The node at index start offset &minus; 1 might be an invisible
+  node.
+
+  <!--
+  At the beginning of an indented block, outdent it, similar to a list item.
+  Browsers don't do this, word processors do.
+  -->
+  <li>If <var>offset</var> is zero, and <var>node</var> has an
+  [[ancestorcontainer]] that is both a <span>potential indentation
+  element</span> and a [[descendant]] of <var>start node</var>:
+
+  <p class=XXX>This copy-pastes from the outdent command action.  I'm also not
+  totally sure it's correct.
+
+  <ol>
+    <li><span>Block-extend</span> the [[range]] whose [[rangestart]] and
+    [[rangeend]] are both (<var>node</var>, 0), and let <var>new range</var> be
+    the result.
+
+    <li>Let <var>node list</var> be a list of [[nodes]], initially empty.
+
+    <li>For each [[node]] <var>current node</var> [[contained]] in <var>new
+    range</var>, append <var>current node</var> to <var>node list</var> if the
+    last member of <var>node list</var> (if any) is not an [[ancestor]] of
+    <var>current node</var>, and <var>current node</var> is
+    <span>editable</span> but has no <span>editable</span> [[descendants]].
+
+    <li><span>Outdent</span> each [[node]] in <var>node list</var>.
+
+    <li>Abort these steps.
+  </ol>
+
+  <!--
+  This is to avoid stripping a line break from
+
+    foo<br><br><table><tr><td>[]bar</table>
+
+  and similarly for <hr>.  We should just do nothing here.
+  -->
+  <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
+  offset</var> is a [[table]], abort these steps.
+
+  <!--
+  If you try backspacing into a table, select it.  This doesn't match any
+  browser; it matches the recommendation of the "behavior when typing in
+  contentEditable elements" document.  The idea is that then you can delete it
+  with a second backspace.
+  -->
+  <li>If <var>start node</var> has a [[child]] with [[index]] <var>start
+  offset</var> &minus; 1, and that [[child]] is a [[table]]:
+
+  <ol>
+    <li>Call [[selcollapse|<var>start node</var>, <var>start offset</var>
+    &minus; 1]] on the [[contextobject]]'s [[selection]].
+
+    <li>Call [[extend|<var>start node</var>, <var>start offset</var>]] on the
+    [[contextobject]]'s [[selection]].
+
+    <li>Abort these steps.
+  </ol>
+
+  <!--
+  Special case:
+
+    <p>foo</p><br><p>[]bar</p>
+    -> <p>foo</p><p>[]bar</p>
+
+  and likewise for <hr>.  But with <img> we merge like in other cases:
+
+    <p>foo</p><img><p>[]bar</p>
+    -> <p>foo</p><img>[]bar.
+
+  Browsers don't do this consistently.  Firefox 5.0a2 doesn't seem to do it at
+  all.
+  -->
+  <li>If <var>offset</var> is zero; and either the [[child]] of <var>start
+  node</var> with [[index]] <var>start offset</var> minus one is an [[hr]], or
+  the [[child]] is a [[br]] whose [[previoussibling]] is either a [[br]] or not
+  an <span>inline node</span>:
+
+  <ol>
+    <li>Call [[selcollapse|<var>node</var>, <var>offset</var>]] on the
+    [[selection]].
+
+    <li><span>Delete the contents</span> of the [[range]] with [[rangestart]]
+    (<var>start node</var>, <var>start offset</var> &minus; 1) and [[rangeend]]
+    (<var>start node</var>, <var>start offset</var>).
+
+    <li>Abort these steps.
+  </ol>
+
+  <!--
+  If you try backspacing out of a list item, merge it with the previous item,
+  but add a line break.  Then you have to backspace again if you really want
+  them to be on the same line.  This matches Word 2007 and OpenOffice.org 3.2.1
+  Ubuntu, and also matches "behavior when typing in contentEditable elements",
+  but does not match any browser.
+
+  Note that this behavior is quite different from what happens if you actually
+  select the linebreak in between the two lines.  In that case, the blocks are
+  merged as normal.
+
+  Also note that hitting backspace twice will merge with the previous item.
+  This matches OO.org, but Word will outdent the item on subsequent backspaces.
+  Word's behavior doesn't fit well with the way lists work in HTML, and we
+  probably don't want it.
+  -->
+  <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
+  offset</var> is an [[li]] or [[dt]] or [[dd]], and that [[child]]'s
+  [[firstchild]] is an <span>inline node</span>, and <var>start offset</var> is
+  not zero:
+
+  <ol>
+    <li>Let <var>previous item</var> be the [[child]] of <var>start node</var>
+    with [[index]] <var>start offset</var> minus one.
+
+    <!-- If the last child is already a br, we only need to append one extra
+    br.  Otherwise we need to append two, since the first will do nothing. -->
+    <li>If <var>previous item</var>'s [[lastchild]] is an <span>inline
+    node</span> other than a [[br]], call [[createelement|"br"]] on the
+    [[contextobject]] and append the result as the last [[child]] of
+    <var>previous item</var>.
+
+    <li>If <var>previous item</var>'s [[lastchild]] is an <span>inline
+    node</span>, call [[createelement|"br"]] on the [[contextobject]] and
+    append the result as the last [[child]] of <var>previous item</var>.
+  </ol>
+
+  <!--
+  When merging adjacent list items, make sure we only merge the items
+  themselves, not any block children.  We want <li><p>foo<li><p>bar to become
+  <li><p>foo<p>bar, not <li><p>foo<br>bar or <li><p>foobar.
+  -->
+  <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
+  offset</var> is an [[li]] or [[dt]] or [[dd]], and its [[previoussibling]] is
+  also an [[li]] or [[dt]] or [[dd]], set <var>start node</var> to its
+  [[child]] with [[index]] <var>start offset</var> &minus; 1, then set
+  <var>start offset</var> to <var>start node</var>'s [[nodelength]], then set
+  <var>node</var> to <var>start node</var>'s [[nextsibling]], then set
+  <var>offset</var> to 0.
+
+  <!-- General block-merging case. -->
+  <li>Otherwise, while <var>start node</var> has a [[child]] with [[index]]
+  <var>start offset</var> minus one, set <var>start node</var> to that
+  [[child]], then set <var>start offset</var> to the [[nodelength]] of
+  <var>start node</var>.
+
+  <li><span>Delete the contents</span> of the [[range]] with [[rangestart]]
+  (<var>start node</var>, <var>start offset</var>) and [[rangeend]]
+  (<var>node</var>, <var>offset</var>).
+</ol>
+
+
 <h3><dfn>The <code title>formatBlock</code> command</dfn></h3>
 <!--
 Tested browser versions: IE9, Firefox 4.0, Chrome 13 dev, Opera 11.10.