Define how commands mutate ranges
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Wed, 06 Apr 2011 12:51:05 -0600
changeset 39 fd250049ac86
parent 38 edb0122f35f2
child 40 7cbe5f9daa66
Define how commands mutate ranges

Works pretty well, although I'm not sure it's exactly what we want.
It's enough to let me define workable subscript and superscript
commands, which don't behave strangely with nesting and so on.
autoimplementation.html
editcommands.html
implementation.js
preprocess
source.html
--- a/autoimplementation.html	Sun Mar 27 17:59:22 2011 -0600
+++ b/autoimplementation.html	Wed Apr 06 12:51:05 2011 -0600
@@ -26,10 +26,13 @@
 href=http://www.browserscope.org/richtext2/test>Browserscope</a>.  data-start
 and data-end attributes also represent element boundary points, with the node
 being the element with the attribute and the offset given as the attribute
-value, for cases where HTML parsing doesn't allow text nodes.  Where two tables
-are given for a single command, the first is with styleWithCSS false and the
-second is with it true.  (The second table is left blank in IE and Opera, which
-don't support styleWithCSS.)
+value, for cases where HTML parsing doesn't allow text nodes.  Currently we
+don't really pay attention to reversed selections at all, so they might get
+displayed as forwards or such.
+
+<p>Where two tables are given for a single command, the first is with
+styleWithCSS false and the second is with it true.  (The second table is left
+blank in IE and Opera, which don't support styleWithCSS.)
 
 <h1>Table of Contents</h1>
 <ul>
@@ -40,9 +43,12 @@
 	<li><a href=#hilitecolor>hilitecolor</a>
 	<li><a href=#italic>italic</a>
 	<li><a href=#subscript>subscript</a>
+	<li><a href=#superscript>superscript</a>
 	<li><a href=#underline>underline</a>
 </ul>
 
+<button onclick="for (var command in tests) runTests(command)">Run all tests</button>
+
 <div id=backcolor>
 <h1>backcolor</h1>
 
@@ -128,14 +134,23 @@
 
 <button onclick="runTests('subscript')">Run tests</button>
 
-<p><strong>Note:</strong> The subscript spec is still buggy and unstable, so
-don't pay much attention to what's in the spec column.
+<table border=1><tr><th>Input <th>Spec <th>Browser <th>Same?</table>
+<table border=1><tr><th>Input <th>Spec <th>Browser <th>Same?</table>
+
+<p><label>Enter new test here: <input></label>
+<button onclick="addTest('subscript', document.querySelector('#subscript input').value)">Add test</button>
+</div>
+
+<div id=superscript>
+<h1>superscript</h1>
+
+<button onclick="runTests('superscript')">Run tests</button>
 
 <table border=1><tr><th>Input <th>Spec <th>Browser <th>Same?</table>
 <table border=1><tr><th>Input <th>Spec <th>Browser <th>Same?</table>
 
 <p><label>Enter new test here: <input></label>
-<button onclick="addTest('subscript', document.querySelector('#subscript input').value)">Add test</button>
+<button onclick="addTest('superscript', document.querySelector('#superscript input').value)">Add test</button>
 </div>
 
 <div id=underline>
@@ -160,6 +175,7 @@
 	hilitecolor: "#FF8888",
 	italic: null,
 	subscript: null,
+	superscript: null,
 	underline: null,
 };
 
@@ -478,6 +494,37 @@
 		'foo<sup><sub>b[a]r</sub></sup>baz',
 		'foo<sup>b<sub>[a]</sub>r</sup>baz',
 	],
+	superscript: [
+		'foo[bar]baz',
+		'foo]bar[baz',
+		'{<p><p> <p>foo</p>}',
+		'foo[bar<b>baz]qoz</b>quz',
+
+		'<table><tbody><tr><td>foo<td>b[a]r<td>baz</table>',
+		'<table><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table>',
+		'<table><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table>',
+		'<table><tbody data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table>',
+		'<table data-start=0 data-end=1><tbody><tr><td>foo<td>bar<td>baz</table>',
+		'{<table><tr><td>foo<td>bar<td>baz</table>}',
+
+		'foo<sub>[bar]</sub>baz',
+		'foo<sub>b[a]r</sub>baz',
+		'foo<sup>[bar]</sup>baz',
+		'foo<sup>b[a]r</sup>baz',
+
+		'foo<sub><sub>[bar]</sub></sub>baz',
+		'foo<sub><sub>b[a]r</sub></sub>baz',
+		'foo<sub>b<sub>[a]</sub>r</sub>baz',
+		'foo<sup><sup>[bar]</sup></sup>baz',
+		'foo<sup><sup>b[a]r</sup></sup>baz',
+		'foo<sup>b<sup>[a]</sup>r</sup>baz',
+		'foo<sub><sup>[bar]</sup></sub>baz',
+		'foo<sub><sup>b[a]r</sup></sub>baz',
+		'foo<sub>b<sup>[a]</sup>r</sub>baz',
+		'foo<sup><sub>[bar]</sub></sup>baz',
+		'foo<sup><sub>b[a]r</sub></sup>baz',
+		'foo<sup>b<sub>[a]</sub>r</sup>baz',
+	],
 	underline: [
 		'foo[bar]baz',
 		'foo]bar[baz',
@@ -522,8 +569,8 @@
 
 function addTest(command, test) {
 	var doubleTesting = ["backcolor", "bold", "italic", "underline",
-	"forecolor", "fontname", "fontsize", "superscript",
-	"subscript"].indexOf(command) != -1;
+	"forecolor", "fontname", "fontsize", "subscript",
+	"superscript"].indexOf(command) != -1;
 
 	// I tried to feature-detect styleWithCSS, but it was too much of a pain,
 	// with Firefox randomly throwing exceptions all over the place.  So I'm
@@ -593,6 +640,7 @@
 		}
 		myExecCommand("styleWithCSS", false, styleWithCss);
 		myExecCommand(command, false, value, range);
+		addBrackets(range);
 		specCell.lastChild.textContent = specCell.firstChild.innerHTML;
 	} catch (e) {
 		specCell.lastChild.style.color = "red";
@@ -637,6 +685,7 @@
 			document.execCommand("styleWithCSS", false, styleWithCss);
 		} catch (e) {}
 		document.execCommand(command, false, value);
+		addBrackets(getSelection().getRangeAt(0));
 		browserCell.lastChild.textContent = browserCell.firstChild.innerHTML;
 	} catch (e) {
 		browserCell.textContent = "Exception: " + e;
@@ -850,4 +899,41 @@
 		}
 	}
 }
+
+/**
+ * Add brackets at the start and end points of the given range, so that they're
+ * visible.
+ */
+function addBrackets(range) {
+	// Do the end first, so that if the range is collapsed, the start point
+	// will be inserted before the end point.
+	if (range.endContainer.nodeType == Node.TEXT_NODE) {
+		range.endContainer.insertData(range.endOffset, "]");
+	} else {
+		// As everyone knows, the only node types are Text and Element.
+		if (range.endOffset != range.endContainer.childNodes.length
+		&& range.endContainer.childNodes[range.endOffset].nodeType == Node.TEXT_NODE) {
+			range.endContainer.childNodes[range.endOffset].insertData(0, "}");
+		} else if (range.endOffset != 0
+		&& range.endContainer.childNodes[range.endOffset - 1].nodeType == Node.TEXT_NODE) {
+			range.endContainer.childNodes[range.endOffset - 1].appendData("}");
+		} else {
+			// No idea how this will work for tables . . .
+			range.endContainer.insertBefore(document.createTextNode("}"), range.endContainer.childNodes[range.endOffset]);
+		}
+	}
+	if (range.startContainer.nodeType == Node.TEXT_NODE) {
+		range.startContainer.insertData(range.startOffset, "[");
+	} else {
+		if (range.startOffset != range.startContainer.childNodes.length
+		&& range.startContainer.childNodes[range.startOffset].nodeType == Node.TEXT_NODE) {
+			range.startContainer.childNodes[range.startOffset].insertData(0, "{");
+		} else if (range.startOffset != 0
+		&& range.startContainer.childNodes[range.startOffset - 1].nodeType == Node.TEXT_NODE) {
+			range.startContainer.childNodes[range.startOffset - 1].appendData("{");
+		} else {
+			range.startContainer.insertBefore(document.createTextNode("{"), range.startContainer.childNodes[range.startOffset]);
+		}
+	}
+}
 </script>
--- a/editcommands.html	Sun Mar 27 17:59:22 2011 -0600
+++ b/editcommands.html	Wed Apr 06 12:51:05 2011 -0600
@@ -27,7 +27,7 @@
 <body class=draft>
 <div class=head id=head>
 <h1>HTML Editing Commands</h1>
-<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-27-march-2011>Work in Progress &mdash; Last Update 27 March 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-6-april-2011>Work in Progress &mdash; Last Update 6 April 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;ayg+spec@aryeh.name&gt;
@@ -147,16 +147,19 @@
   <li>Also not sure about computed style.  There are differences between
   "computed" and "used" and things like that, what do we actually want here?
 
-  <li>The wording I use for DOM stuff is a bit of a mess, often either
-  imprecise or unreasonably verbose.  I'm not quite sure how to fix it.
+  <li>The wording I use for DOM stuff is not maximally precise.  Really I want
+  DOM Core to define nice concepts that I can xref, like "insert a node".  I
+  don't want to have to explicitly refer to DOM methods like insertBefore()
+  every time I want to move things.
 
   <li>I haven't put any thought yet into collapsed ranges or selections.
   Currently my algorithms mostly do nothing if the selection is collapsed,
   which is of course wrong.  E.g., bold with collapsed selection should put
   &lt;b&gt;&lt;/b&gt; at the cursor, generally.
 
-  <li>I also don't pay attention to what happens to the selection when you
-  mutate the DOM.  This is essential.
+  <li>Some more thought needs to go into what happens to the selection when you
+  mutate the DOM.  In some cases the results are pretty arbitrary.  It might
+  make sense to do some kind of normalization.
 
   <li>JavaScript can modify the DOM synchronously in some cases, such as DOM
   mutation events and onunload when moving around iframes and objects.  This
@@ -421,6 +424,34 @@
 <a href=#styling-element>styling element</a> which <a href=#specified-style title="specified
 style">specifies</a> at most one CSS property.
 
+<p>When the user agent is to move a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> to a new location, <dfn id=preserving-ranges>preserving
+ranges</dfn>, it must remove the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></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>, then
+insert it in the new location.  In doing so, however, it must 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 href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></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> 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>The <dfn id=css-styling-flag>CSS styling flag</dfn> is a boolean flag, which must initially be
 false.
 
@@ -444,57 +475,22 @@
 <p>When a user agent is to <dfn id=decompose>decompose</dfn> a <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> <var title="">range</var>,
 it must run the following steps.
 
+<p class=note>For this algorithm to be correct, it is essential that user
+agents follow the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#range-mutation-rules>range mutation rules</a>, particularly those for <code title="">splitText()</code>.
+
 <ol>
   <li>If <var title="">range</var>'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> 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 the same,
   return an empty list.
 
-  <li>Let <var title="">start node</var>, <var title="">start offset</var>, <var title="">end node</var>,
-  and <var title="">end offset</var> be <var title="">range</var>'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> 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> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>nodes</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>offsets</a>, respectively.
-
-  <li>If <var title="">start node</var> is a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> node and is the same as <var title="">end
-  node</var>, and <var title="">start offset</var> is neither 0 nor 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>:
-
-  <ol>
-    <li>Set <var title="">start node</var> to the result of running <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-text-splittext><code class=external data-anolis-spec=domcore title=dom-Text-splitText>splitText(<var title="">start
-    offset</var>)</code></a> on <var title="">start node</var>.
-
-    <li>Set <var title="">end node</var> to <var title="">start node</var>.
-
-    <li>Set <var title="">end offset</var> to <var title="">end offset</var> &minus; <var title="">start
-    offset</var>.
-
-    <li>Set <var title="">start offset</var> to 0.
-  </ol>
-
-  <li>Otherwise, if <var title="">start node</var> is a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> node and <var title="">start
-  offset</var> is neither 0 nor 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>If <var title="">range</var>'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> is a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> node and
+  its <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-offset title=concept-boundary-point-offset>offset</a> is neither 0 nor 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 its
+  <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>, run <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-text-splittext><code class=external data-anolis-spec=domcore title=dom-Text-splitText>splitText()</code></a> on its <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>
+  with argument equal to its <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-offset title=concept-boundary-point-offset>offset</a>.
 
-  <ol>
-    <li>Set <var title="">start node</var> to the result of running <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-text-splittext><code class=external data-anolis-spec=domcore title=dom-Text-splitText>splitText(<var title="">start
-    offset</var>)</code></a> on <var title="">start node</var>.
-
-    <li>Set <var title="">start offset</var> to 0.
-  </ol>
-
-  <li>If <var title="">end node</var> is a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> node and <var title="">end offset</var> is
-  neither 0 nor 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="">end node</var>, run <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-text-splittext><code class=external data-anolis-spec=domcore title=dom-Text-splitText>splitText(<var title="">end
-  offset</var>)</code></a> on <var title="">end node</var>.
-
-  <!-- The next two steps ensure that our fragmented text nodes are contained
-  in the range. -->
-  <li>If <var title="">start node</var> is a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> node and <var title="">start offset</var>
-  is 0, 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>,
-  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>.
-
-  <li>If <var title="">end node</var> is a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> node and <var title="">end offset</var> is
-  its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a>, set <var title="">end offset</var> to one plus 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="">end node</var>, then set <var title="">end node</var> to its parent.
-
-  <li>Set <var title="">range</var>'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> to (<var title="">start node</var>,
-  <var title="">start offset</var>) and its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> to (<var title="">end node</var>,
-  <var title="">end offset</var>).
+  <li>If <var title="">range</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</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> is a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> node and
+  its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> <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 neither 0 nor 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 its
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</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>, run <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-text-splittext><code class=external data-anolis-spec=domcore title=dom-Text-splitText>splitText()</code></a> on its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</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>
+  with argument equal to its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> <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>.
 
   <!-- Now we want to make sure our range contains as many nodes as possible,
   such as by changing <tag>[foo]</tag> to {<tag>foo</tag>}. -->
@@ -545,20 +541,17 @@
   <li>If <var title="">element</var> is a <a href=#simple-styling-element>simple styling element</a>:
 
   <ol>
-    <li>Let <var title="">children</var> be an empty list of <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a>s.
-
-    <li>While <var title="">element</var> has children:
+    <li>Let <var title="">children</var> be the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a> of <var title="">element</var>.
 
-    <ol>
-      <li>Let <var title="">child</var> be the first child of <var title="">element</var>.
-
-      <li>Append <var title="">child</var> to <var title="">children</var>.
-
-      <li>Insert <var title="">child</var> as the previous sibling of
-      <var title="">element</var>.
-    </ol>
+    <li>While <var title="">element</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, insert its first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>
+    into 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> immediately before it, <a href=#preserving-ranges>preserving ranges</a>.
 
     <li>Remove <var title="">element</var> from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+    <!-- Notice that a boundary point that was immediately before or after the
+    element will now be immediately before or after its children, just because
+    of the regular range mutation rules, without needing to worry about
+    preserving ranges.  Preserving ranges is only necessary for the sake of
+    boundary points in the element or its descendants. -->
 
     <li>Return <var title="">children</var>.
   </ol>
@@ -590,11 +583,15 @@
   <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> "span", with the same attributes and <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-ownerdocument><code class=external data-anolis-spec=domcore title=dom-Node-ownerDocument>ownerDocument</code></a> as
   <var title="">element</var>.
 
-  <li>Append <var title="">new element</var> to <var title="">element</var>'s
-  parent as the previous sibling of <var title="">element</var>.
+  <li>Insert <var title="">new element</var> into the <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> of <var title="">element</var>
+  immediately before it.
+  <!-- This means that the boundary point immediately before the element goes
+  before the new element, and after we remove the old element, the boundary
+  point immediately after the old element will be immediately after the new
+  element, because of the regular range mutation rules. -->
 
-  <li>While <var title="">element</var> has children, append its first child
-  as the last child of <var title="">new element</var>.
+  <li>While <var title="">element</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, append its first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> as
+  the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">new element</var>, <a href=#preserving-ranges>preserving ranges</a>.
 
   <li>Remove <var title="">element</var> from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
 
@@ -757,15 +754,21 @@
     is not the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling><code class=external data-anolis-spec=domcore title=dom-Node-previousSibling>previousSibling</code></a> of <var title="">node</var>:
 
     <ol>
-      <li>While <var title="">candidate</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, append the first
-      <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">candidate</var> as the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of
-      <var title="">candidate</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+      <li>While <var title="">candidate</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, insert the first
+      <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">candidate</var> into <var title="">candidate</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>
+      immediately before <var title="">candidate</var>, <a href=#preserving-ranges>preserving ranges</a>.
 
       <li>Insert <var title="">candidate</var> into <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> before
-      <var title="">node</var>.
+      <var title="">node</var>'s <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling><code class=external data-anolis-spec=domcore title=dom-Node-previousSibling>previousSibling</code></a>.  <!-- If candidate had no
+      children, any boundary point inside it will get moved to its parent here,
+      which is okay.  We don't want to preserve ranges, because that would move
+      boundary points that originally were in candidate but were moved to its
+      parent by the last step to move to node's parent.  We move to before the
+      previous sibling so that boundary points before and after the previous
+      sibling wind up before or after candidate. -->
 
-      <li>Append the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling><code class=external data-anolis-spec=domcore title=dom-Node-previousSibling>previousSibling</code></a> of <var title="">candidate</var> as the last
-      <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">candidate</var>.
+      <li>Append the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling><code class=external data-anolis-spec=domcore title=dom-Node-nextSibling>nextSibling</code></a> of <var title="">candidate</var> as the last
+      <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">candidate</var>, <a href=#preserving-ranges>preserving ranges</a>.
     </ol>
 
     <li>Let <var title="">candidate</var> be <var title="">node</var>'s <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling><code class=external data-anolis-spec=domcore title=dom-Node-nextSibling>nextSibling</code></a>.
@@ -783,15 +786,19 @@
     is not the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling><code class=external data-anolis-spec=domcore title=dom-Node-nextSibling>nextSibling</code></a> of <var title="">node</var>:
 
     <ol>
-      <li>While <var title="">candidate</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, append the first
-      <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">candidate</var> as the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of
-      <var title="">candidate</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+      <li>While <var title="">candidate</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, insert the first
+      <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">candidate</var> into <var title="">candidate</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>
+      immediately before <var title="">candidate</var>, <a href=#preserving-ranges>preserving ranges</a>.
 
       <li>Insert <var title="">candidate</var> into <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> after
-      <var title="">node</var>.
+      <var title="">node</var>. <!-- Thus candidate is between the same boundary points
+      as node's next sibling, not the same as node.  When inserting, the new
+      thing always gets put in the same place as its next sibling, not its
+      previous sibling: a boundary point at the place it's inserted moves
+      before the new node, not after. -->
 
       <li>Append the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling><code class=external data-anolis-spec=domcore title=dom-Node-nextSibling>nextSibling</code></a> of <var title="">candidate</var> as the last
-      <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">candidate</var>.
+      <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="">candidate</var>, <a href=#preserving-ranges>preserving ranges</a>.
     </ol>
 
     <li>Let <var title="">previous sibling</var> and <var title="">next sibling</var> be
@@ -800,7 +807,8 @@
     <li>If <var title="">previous sibling</var> is a <a href=#simple-styling-element>simple styling element</a>
     whose <a href=#specified-style>specified style</a> and <a href=#effective-style>effective style</a> for
     <var title="">property</var> are both <var title="">new value</var>, append <var title="">node</var>
-    as the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">previous sibling</var>.
+    as the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">previous sibling</var>, <a href=#preserving-ranges>preserving
+    ranges</a>.
 
     <li>If <var title="">next sibling</var> is a <a href=#simple-styling-element>simple styling element</a>
     whose <a href=#specified-style>specified style</a> and <a href=#effective-style>effective style</a> for
@@ -808,12 +816,13 @@
 
     <ol>
       <li>If <var title="">node</var> is not 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> of <var title="">previous sibling</var>,
-      insert <var title="">node</var> as the first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">next sibling</var>.
+      insert <var title="">node</var> as the first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">next
+      sibling</var>, <a href=#preserving-ranges>preserving ranges</a>.
 
       <li>Otherwise, while <var title="">next sibling</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, append the
       first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">next sibling</var> as the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of
-      <var title="">previous sibling</var>.  Then remove <var title="">next sibling</var> from
-      its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+      <var title="">previous sibling</var>, <a href=#preserving-ranges>preserving ranges</a>.  Then remove
+      <var title="">next sibling</var> from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
     </ol>
   </ol>
 
@@ -901,20 +910,22 @@
   <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-ownerdocument><code class=external data-anolis-spec=domcore title=dom-Node-ownerDocument>ownerDocument</code></a> of <var title="">node</var>.
 
   <li>Insert <var title="">new parent</var> in <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> before
-  <var title="">node</var>.
+  <var title="">node</var>.  <!-- This preserves boundary points correctly, as usual.
+  -->
 
   <li>If the <a href=#effective-style>effective style</a> of <var title="">property</var> for <var title="">new
   parent</var> is not <var title="">new value</var>, set the CSS property
   <var title="">property</var> of <var title="">new parent</var> to <var title="">new value</var>.
 
-  <li>Append <var title="">node</var> to <var title="">new parent</var> as its last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>.
+  <li>Append <var title="">node</var> to <var title="">new parent</var> as its last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>,
+  <a href=#preserving-ranges>preserving ranges</a>.
 
   <li>If <var title="">node</var> is an <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> and the <a href=#effective-style>effective style</a>
   of <var title="">property</var> for <var title="">node</var> is not <var title="">new value</var>:
 
   <ol>
     <li>Insert <var title="">node</var> into the <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> of <var title="">new parent</var>
-    before <var title="">new parent</var>.
+    before <var title="">new parent</var>, <a href=#preserving-ranges>preserving ranges</a>.
 
     <li>Remove <var title="">new parent</var> from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
 
@@ -1444,12 +1455,8 @@
 state of the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> for this command is then true, <a href=#style>style</a> each
 returned <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> with <var title="">property</var> "vertical-align" and <var title="">new
 value</var> "baseline".  Otherwise, <a href=#style>style</a> them with <var title="">new
-value</var> "baseline", then <a href=#style>style</a> them again with <var title="">new
-value</var> "sub".
-
-<p class=XXX>This doesn't work, because we'll have removed some of the things
-that we want to style in some cases.  Need to define what happens to a range
-when you style its nodes, then re-decompose the same range before re-styling.
+value</var> "baseline", then <a href=#decompose>decompose</a> the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> again and
+<a href=#style>style</a> each returned <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> with <var title="">new value</var> "sub".
 
 <dd><strong>State</strong>: True if every <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> node that is
 <a href=#effectively-contained>effectively contained</a> in the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> has <a href=#effective-style>effective
@@ -1464,8 +1471,8 @@
 state of the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> for this command is then true, <a href=#style>style</a> each
 returned <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> with <var title="">property</var> "vertical-align" and <var title="">new
 value</var> "baseline".  Otherwise, <a href=#style>style</a> them with <var title="">new
-value</var> "baseline", then <a href=#style>style</a> them again with <var title="">new
-value</var> "super".
+value</var> "baseline", then <a href=#decompose>decompose</a> the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> again and
+<a href=#style>style</a> each returned <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> with <var title="">new value</var> "super".
 
 <dd><strong>State</strong>: True if every <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> node that is
 <a href=#effectively-contained>effectively contained</a> in the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> has <a href=#effective-style>effective
--- a/implementation.js	Sun Mar 27 17:59:22 2011 -0600
+++ b/implementation.js	Wed Apr 06 12:51:05 2011 -0600
@@ -37,6 +37,26 @@
 	return node.nextSibling;
 }
 
+/**
+ * Returns true if ancestor is an ancestor of descendant, false otherwise.
+ */
+function isAncestor(ancestor, descendant) {
+	if (!ancestor || !descendant) {
+		return false;
+	}
+	while (descendant && descendant != ancestor) {
+		descendant = descendant.parentNode;
+	}
+	return descendant == ancestor;
+}
+
+/**
+ * Returns true if descendant is a descendant of ancestor, false otherwise.
+ */
+function isDescendant(descendant, ancestor) {
+	return isAncestor(ancestor, descendant);
+}
+
 function convertProperty(property) {
 	// Special-case for now
 	var map = {
@@ -651,6 +671,75 @@
 	return false;
 }
 
+function movePreservingRanges(node, newParent, newIndex) {
+	// "When the user agent is to move a Node to a new location, preserving
+	// ranges, it must remove the Node from its original parent, 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
+	// 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 decomposeRange(range) {
 	// "If range's start and end are the same, return an empty list."
 	if (range.startContainer == range.endContainer
@@ -658,74 +747,34 @@
 		return [];
 	}
 
-	// "Let start node, start offset, end node, and end offset be range's start
-	// and end nodes and offsets, respectively."
-	var startNode = range.startContainer;
-	var startOffset = range.startOffset;
-	var endNode = range.endContainer;
-	var endOffset = range.endOffset;
-
-	// "If start node is a Text node and is the same as end node, and start
-	// offset is neither 0 nor the length of start node:"
-	if (startNode.nodeType == Node.TEXT_NODE
-	&& startNode == endNode
-	&& startOffset != 0
-	&& startOffset != getNodeLength(startNode)) {
-		// "Set start node to the result of running splitText(start offset) on
-		// start node."
-		startNode = startNode.splitText(startOffset);
-
-		// "Set end node to start node."
-		endNode = startNode;
-
-		// "Set end offset to end offset − start offset."
-		endOffset -= startOffset;
-
-		// "Set start offset to 0."
-		startOffset = 0;
-
-	// "Otherwise, if start node is a Text node and start offset is neither 0
-	// nor the length of start node:"
-	} else if (startNode.nodeType == Node.TEXT_NODE
-	&& startOffset != 0
-	&& startOffset != getNodeLength(startNode)) {
-		// "Set start node to the result of running splitText(start offset) on
-		// start node."
-		startNode = startNode.splitText(startOffset);
-
-		// "Set start offset to 0."
-		startOffset = 0;
+	// "If range's start node is a Text node and its start offset is neither 0
+	// nor the length of its start node, run splitText() on its start node with
+	// argument equal to its start offset."
+	if (range.startContainer.nodeType == Node.TEXT_NODE
+	&& range.startOffset != 0
+	&& range.startOffset != getNodeLength(range.startContainer)) {
+		// Account for UAs not following range mutation rules
+		if (range.startContainer == range.endContainer) {
+			var newEndOffset = range.endOffset - range.startOffset;
+			var newText = range.startContainer.splitText(range.startOffset);
+			range.setStart(newText, 0);
+			range.setEnd(newText, newEndOffset);
+		} else {
+			var newText = range.startContainer.splitText(range.startOffset);
+			range.setStart(newText, 0);
+		}
 	}
 
-	// "If end node is a Text node and end offset is neither 0 nor the length
-	// of end node, run splitText(end offset) on end node."
-	if (endNode.nodeType == Node.TEXT_NODE
-	&& endOffset != 0
-	&& endOffset != getNodeLength(endNode)) {
-		endNode.splitText(endOffset);
+	// "If range's end node is a Text node and its end offset is neither 0 nor
+	// the length of its end node, run splitText() on its end node with
+	// argument equal to its end offset."
+	if (range.endContainer.nodeType == Node.TEXT_NODE
+	&& range.endOffset != 0
+	&& range.endOffset != getNodeLength(range.endContainer)) {
+		range.endContainer.splitText(range.endOffset);
+		// No correction should be needed here
 	}
 
-	// "If start node is a Text node and start offset is 0, set start offset to
-	// the index of start node, then set start node to its parent."
-	if (startNode.nodeType == Node.TEXT_NODE
-	&& startOffset == 0) {
-		startOffset = getNodeIndex(startNode);
-		startNode = startNode.parentNode;
-	}
-
-	// "If end node is a Text node and end offset is its length, set end offset
-	// to one plus the index of end node, then set end node to its parent."
-	if (endNode.nodeType == Node.TEXT_NODE
-	&& endOffset == getNodeLength(endNode)) {
-		endOffset = 1 + getNodeIndex(endNode);
-		endNode = endNode.parentNode;
-	}
-
-	// "Set range's start to (start node, start offset) and its end to (end
-	// node, end offset)."
-	range.setStart(startNode, startOffset);
-	range.setEnd(endNode, endOffset);
-
 	// "Let cloned range be the result of calling cloneRange() on range."
 	var clonedRange = range.cloneRange();
 
@@ -767,19 +816,13 @@
 
 	// "If element is a simple styling element:"
 	if (isSimpleStylingElement(element)) {
-		// "Let children be an empty list of Nodes."
-		var children = [];
+		// "Let children be the children of element."
+		var children = Array.prototype.slice.call(element.childNodes);
 
-		// "While element has children:"
-		while (element.hasChildNodes()) {
-			// "Let child be the first child of element."
-			var child = element.firstChild;
-
-			// "Append child to children."
-			children.push(child);
-
-			// "Insert child as the previous sibling of element."
-			element.parentNode.insertBefore(child, element);
+		// "While element has children, insert its first child into its parent
+		// immediately before it, preserving ranges."
+		while (element.childNodes.length) {
+			movePreservingRanges(element.firstChild, element.parentNode, getNodeIndex(element));
 		}
 
 		// "Remove element from its parent."
@@ -829,14 +872,13 @@
 		newElement.setAttribute(element.attributes[j].localName, element.attributes[j].value);
 	}
 
-	// "Append new element to element's parent as the previous sibling of
-	// element."
+	// "Insert new element into the parent of element immediately before it."
 	element.parentNode.insertBefore(newElement, element);
 
-	// "While element has children, append its first child as the last
-	// child of new element."
-	while (element.hasChildNodes()) {
-		newElement.appendChild(element.firstChild);
+	// "While element has children, append its first child as the last child of
+	// new element, preserving ranges."
+	while (element.childNodes.length) {
+		movePreservingRanges(element.firstChild, newElement, newElement.childNodes.length);
 	}
 
 	// "Remove element from its parent."
@@ -907,10 +949,7 @@
 		}
 
 		// "Let children be the children of current ancestor."
-		var children = [];
-		for (var i = 0; i < currentAncestor.childNodes.length; i++) {
-			children.push(currentAncestor.childNodes[i]);
-		}
+		var children = Array.prototype.slice.call(currentAncestor.childNodes);
 
 		// "Clear styles on current ancestor."
 		clearStyles(currentAncestor, property);
@@ -983,18 +1022,20 @@
 		&& cssValuesEqual(property, getSpecifiedStyle(candidate, property), newValue)
 		&& cssValuesEqual(property, getEffectiveStyle(candidate, property), newValue)
 		&& candidate != node.previousSibling) {
-			// "While candidate has children, append the first child of
-			// candidate as the last child of candidate's parent."
+			// "While candidate has children, insert the first child of
+			// candidate into candidate's parent immediately before candidate,
+			// preserving ranges."
 			while (candidate.childNodes.length > 0) {
-				candidate.parentNode.appendChild(candidate.firstChild);
+				movePreservingRanges(candidate.firstChild, candidate.parentNode, getNodeIndex(candidate));
 			}
 
-			// "Insert candidate into node's parent before node."
-			node.parentNode.insertBefore(candidate, node);
+			// "Insert candidate into node's parent before node's
+			// previousSibling."
+			node.parentNode.insertBefore(candidate, node.previousSibling);
 
-			// "Append the previousSibling of candidate as the last child of
-			// candidate."
-			candidate.appendChild(candidate.previousSibling);
+			// "Append the nextSibling of candidate as the last child of
+			// candidate, preserving ranges."
+			movePreservingRanges(candidate.nextSibling, candidate, candidate.childNodes.length);
 		}
 
 		// "Let candidate be node's nextSibling."
@@ -1019,18 +1060,19 @@
 		&& cssValuesEqual(property, getSpecifiedStyle(candidate, property), newValue)
 		&& cssValuesEqual(property, getEffectiveStyle(candidate, property), newValue)
 		&& candidate != node.nextSibling) {
-			// "While candidate has children, append the first child of
-			// candidate as the last child of candidate's parent."
+			// "While candidate has children, insert the first child of
+			// candidate into candidate's parent immediately before candidate,
+			// preserving ranges."
 			while (candidate.childNodes.length > 0) {
-				candidate.parentNode.appendChild(candidate.firstChild);
+				movePreservingRanges(candidate.firstChild, candidate.parentNode, getNodeIndex(candidate));
 			}
 
 			// "Insert candidate into node's parent after node."
 			node.parentNode.insertBefore(candidate, node.nextSibling);
 
 			// "Append the nextSibling of candidate as the last child of
-			// candidate."
-			candidate.appendChild(candidate.nextSibling);
+			// candidate, preserving ranges."
+			movePreservingRanges(candidate.nextSibling, candidate, candidate.childNodes.length);
 		}
 
 		// "Let previous sibling and next sibling be node's previousSibling and
@@ -1040,11 +1082,11 @@
 
 		// "If previous sibling is a simple styling element whose specified
 		// style and effective style for property are both new value, append
-		// node as the last child of previous sibling."
+		// node as the last child of previous sibling, preserving ranges."
 		if (isSimpleStylingElement(previousSibling)
 		&& cssValuesEqual(property, getSpecifiedStyle(previousSibling, property), newValue)
 		&& cssValuesEqual(property, getEffectiveStyle(previousSibling, property), newValue)) {
-			previousSibling.appendChild(node);
+			movePreservingRanges(node, previousSibling, previousSibling.childNodes.length);
 		}
 
 		// "If next sibling is a simple styling element whose specified style
@@ -1053,15 +1095,15 @@
 		&& cssValuesEqual(property, getSpecifiedStyle(nextSibling, property), newValue)
 		&& cssValuesEqual(property, getEffectiveStyle(nextSibling, property), newValue)) {
 			// "If node is not a child of previous sibling, insert node as the
-			// first child of next sibling."
+			// first child of next sibling, preserving ranges."
 			if (node.parentNode != previousSibling) {
-				nextSibling.insertBefore(node, nextSibling.firstChild);
+				movePreservingRanges(node, nextSibling, 0);
 			// "Otherwise, while next sibling has children, append the first
-			// child of next sibling as the last child of previous sibling.
-			// Then remove next sibling from its parent."
+			// child of next sibling as the last child of previous sibling,
+			// preserving ranges.  Then remove next sibling from its parent."
 			} else {
 				while (nextSibling.childNodes.length) {
-					previousSibling.appendChild(nextSibling.firstChild);
+					movePreservingRanges(nextSibling.firstChild, previousSibling, previousSibling.childNodes.length);
 				}
 				nextSibling.parentNode.removeChild(nextSibling);
 			}
@@ -1194,15 +1236,16 @@
 		newParent.style[property] = newValue;
 	}
 
-	// "Append node to new parent as its last child."
-	newParent.appendChild(node);
+	// "Append node to new parent as its last child, preserving ranges."
+	movePreservingRanges(node, newParent, newParent.childNodes.length);
 
 	// "If node is an Element and the effective style of property for node is
 	// not new value:"
 	if (node.nodeType == Node.ELEMENT_NODE
 	&& !cssValuesEqual(property, getEffectiveStyle(node, property), newValue)) {
-		// "Insert node into the parent of new parent before new parent."
-		newParent.parentNode.insertBefore(node, newParent);
+		// "Insert node into the parent of new parent before new parent,
+		// preserving ranges."
+		movePreservingRanges(node, newParent.parentNode, getNodeIndex(newParent));
 
 		// "Remove new parent from its parent."
 		newParent.parentNode.removeChild(newParent);
@@ -1295,10 +1338,7 @@
 	forceStyle(node, property, newValue);
 
 	// "Let children be the children of node."
-	var children = [];
-	for (var i = 0; i < node.childNodes.length; i++) {
-		children.push(node.childNodes[i]);
-	}
+	var children = Array.prototype.slice.call(node.childNodes);
 
 	// "Style each member of children."
 	for (var i = 0; i < children.length; i++) {
@@ -1306,6 +1346,9 @@
 	}
 }
 
+// This is bad :(
+var globalRange = null;
+
 function myExecCommand(commandId, showUI, value, range) {
 	commandId = commandId.toLowerCase();
 
@@ -1319,6 +1362,8 @@
 		}
 	}
 
+	globalRange = range;
+
 	switch (commandId) {
 		case "bold":
 		// "Decompose the Range. If the state of the Range for this command is
@@ -1465,7 +1510,8 @@
 		// "Decompose the Range. If the state of the Range for this command is
 		// then true, style each returned Node with property "vertical-align"
 		// and new value "baseline". Otherwise, style them with new value
-		// "baseline", then style them again with new value "sub"."
+		// "baseline", then decompose the Range again and style each returned
+		// Node with new value "sub"."
 		var nodeList = decomposeRange(range);
 		if (getState(commandId, range)) {
 			for (var i = 0; i < nodeList.length; i++) {
@@ -1475,6 +1521,7 @@
 			for (var i = 0; i < nodeList.length; i++) {
 				styleNode(nodeList[i], "verticalAlign", "baseline");
 			}
+			var nodeList = decomposeRange(range);
 			for (var i = 0; i < nodeList.length; i++) {
 				styleNode(nodeList[i], "verticalAlign", "sub");
 			}
@@ -1485,7 +1532,8 @@
 		// "Decompose the Range. If the state of the Range for this command is
 		// then true, style each returned Node with property "vertical-align"
 		// and new value "baseline". Otherwise, style them with new value
-		// "baseline", then style them again with new value "super"."
+		// "baseline", then decompose the Range again and style each returned
+		// Node with new value "super"."
 		var nodeList = decomposeRange(range);
 		if (getState(commandId, range)) {
 			for (var i = 0; i < nodeList.length; i++) {
@@ -1495,6 +1543,7 @@
 			for (var i = 0; i < nodeList.length; i++) {
 				styleNode(nodeList[i], "verticalAlign", "baseline");
 			}
+			var nodeList = decomposeRange(range);
 			for (var i = 0; i < nodeList.length; i++) {
 				styleNode(nodeList[i], "verticalAlign", "super");
 			}
--- a/preprocess	Sun Mar 27 17:59:22 2011 -0600
+++ b/preprocess	Wed Apr 06 12:51:05 2011 -0600
@@ -47,9 +47,11 @@
     'processinginstruction': '<code data-anolis-spec=domcore>ProcessingInstruction</code>',
     'range': '<code data-anolis-spec=domrange>Range</code>',
     'rangeend': '<span data-anolis-spec=domrange title=concept-range-end>end</span>',
+    'rangemutationrules': '<span data-anolis-spec=domrange>range mutation rules</span>',
     'rangeroot': '<span data-anolis-spec=domrange title=concept-range-root>root</span>',
     'rangestart': '<span data-anolis-spec=domrange title=concept-range-start>start</span>',
     'selection': '<code data-anolis-spec=domrange>Selection</code>',
+    'sibling': '<span data-anolis-spec=domcore title=concept-tree-sibling>sibling</span>',
     'span': '<code data-anolis-spec=html title="the span element">span</code>',
     'strong': '<code data-anolis-spec=html title="the strong element">strong</code>',
     'style': '<code data-anolis-spec=html title="the style attribute">style</code>',
@@ -65,6 +67,11 @@
     s = s.replace("[[" + key + "]]", replace[key])
     # Plurals
     s = s.replace("[[" + key + "s]]", replace[key].replace("</", "s</"))
+    # Capitals
+    capreplace = replace[key].split(">")
+    capreplace[1] = capreplace[1].capitalize()
+    capreplace = ">".join(capreplace)
+    s = s.replace("[[" + key.capitalize() + "]]", capreplace)
 
 if "[[" in s:
     raise Exception("Something mistyped?  " + s[s.find("[["):s.rfind("]]") + 2])
--- a/source.html	Sun Mar 27 17:59:22 2011 -0600
+++ b/source.html	Wed Apr 06 12:51:05 2011 -0600
@@ -134,16 +134,19 @@
   <li>Also not sure about computed style.  There are differences between
   "computed" and "used" and things like that, what do we actually want here?
 
-  <li>The wording I use for DOM stuff is a bit of a mess, often either
-  imprecise or unreasonably verbose.  I'm not quite sure how to fix it.
+  <li>The wording I use for DOM stuff is not maximally precise.  Really I want
+  DOM Core to define nice concepts that I can xref, like "insert a node".  I
+  don't want to have to explicitly refer to DOM methods like insertBefore()
+  every time I want to move things.
 
   <li>I haven't put any thought yet into collapsed ranges or selections.
   Currently my algorithms mostly do nothing if the selection is collapsed,
   which is of course wrong.  E.g., bold with collapsed selection should put
   &lt;b>&lt;/b> at the cursor, generally.
 
-  <li>I also don't pay attention to what happens to the selection when you
-  mutate the DOM.  This is essential.
+  <li>Some more thought needs to go into what happens to the selection when you
+  mutate the DOM.  In some cases the results are pretty arbitrary.  It might
+  make sense to do some kind of normalization.
 
   <li>JavaScript can modify the DOM synchronously in some cases, such as DOM
   mutation events and onunload when moving around iframes and objects.  This
@@ -412,6 +415,34 @@
 <span>styling element</span> which <span title="specified
 style">specifies</span> at most one CSS property.
 
+<p>When the user agent is to move a [[node]] to a new location, <dfn>preserving
+ranges</dfn>, it must remove the [[node]] from its original [[parent]], then
+insert it in the new location.  In doing so, however, it must 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]] 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>The <dfn>CSS styling flag</dfn> is a boolean flag, which must initially be
 false.
 
@@ -435,60 +466,25 @@
 <p>When a user agent is to <dfn>decompose</dfn> a [[range]] <var>range</var>,
 it must run the following steps.
 
+<p class=note>For this algorithm to be correct, it is essential that user
+agents follow the [[rangemutationrules]], particularly those for <code
+title>splitText()</code>.
+
 <ol>
   <li>If <var>range</var>'s [[rangestart]] and [[rangeend]] are the same,
   return an empty list.
 
-  <li>Let <var>start node</var>, <var>start offset</var>, <var>end node</var>,
-  and <var>end offset</var> be <var>range</var>'s [[rangestart]] and
-  [[rangeend]] [[bpnodes]] and [[bpoffsets]], respectively.
-
-  <li>If <var>start node</var> is a [[text]] node and is the same as <var>end
-  node</var>, and <var>start offset</var> is neither 0 nor the [[nodelength]]
-  of <var>start node</var>:
-
-  <ol>
-    <li>Set <var>start node</var> to the result of running <code
-    data-anolis-spec=domcore title=dom-Text-splitText>splitText(<var>start
-    offset</var>)</code> on <var>start node</var>.
-
-    <li>Set <var>end node</var> to <var>start node</var>.
-
-    <li>Set <var>end offset</var> to <var>end offset</var> &minus; <var>start
-    offset</var>.
-
-    <li>Set <var>start offset</var> to 0.
-  </ol>
-
-  <li>Otherwise, if <var>start node</var> is a [[text]] node and <var>start
-  offset</var> is neither 0 nor the [[nodelength]] of <var>start node</var>:
+  <li>If <var>range</var>'s [[rangestart]] [[bpnode]] is a [[text]] node and
+  its [[rangestart]] [[bpoffset]] is neither 0 nor the [[nodelength]] of its
+  [[rangestart]] [[bpnode]], run <code data-anolis-spec=domcore
+  title=dom-Text-splitText>splitText()</code> on its [[rangestart]] [[bpnode]]
+  with argument equal to its [[rangestart]] [[bpoffset]].
 
-  <ol>
-    <li>Set <var>start node</var> to the result of running <code
-    data-anolis-spec=domcore title=dom-Text-splitText>splitText(<var>start
-    offset</var>)</code> on <var>start node</var>.
-
-    <li>Set <var>start offset</var> to 0.
-  </ol>
-
-  <li>If <var>end node</var> is a [[text]] node and <var>end offset</var> is
-  neither 0 nor the [[nodelength]] of <var>end node</var>, run <code
-  data-anolis-spec=domcore title=dom-Text-splitText>splitText(<var>end
-  offset</var>)</code> on <var>end node</var>.
-
-  <!-- The next two steps ensure that our fragmented text nodes are contained
-  in the range. -->
-  <li>If <var>start node</var> is a [[text]] node and <var>start offset</var>
-  is 0, set <var>start offset</var> to the [[index]] of <var>start node</var>,
-  then set <var>start node</var> to its [[parent]].
-
-  <li>If <var>end node</var> is a [[text]] node and <var>end offset</var> is
-  its [[nodelength]], set <var>end offset</var> to one plus the [[index]] of
-  <var>end node</var>, then set <var>end node</var> to its parent.
-
-  <li>Set <var>range</var>'s [[rangestart]] to (<var>start node</var>,
-  <var>start offset</var>) and its [[rangeend]] to (<var>end node</var>,
-  <var>end offset</var>).
+  <li>If <var>range</var>'s [[rangeend]] [[bpnode]] is a [[text]] node and
+  its [[rangeend]] [[bpoffset]] is neither 0 nor the [[nodelength]] of its
+  [[rangeend]] [[bpnode]], run <code data-anolis-spec=domcore
+  title=dom-Text-splitText>splitText()</code> on its [[rangeend]] [[bpnode]]
+  with argument equal to its [[rangeend]] [[bpoffset]].
 
   <!-- Now we want to make sure our range contains as many nodes as possible,
   such as by changing <tag>[foo]</tag> to {<tag>foo</tag>}. -->
@@ -540,20 +536,17 @@
   <li>If <var>element</var> is a <span>simple styling element</span>:
 
   <ol>
-    <li>Let <var>children</var> be an empty list of [[node]]s.
-
-    <li>While <var>element</var> has children:
+    <li>Let <var>children</var> be the [[children]] of <var>element</var>.
 
-    <ol>
-      <li>Let <var>child</var> be the first child of <var>element</var>.
-
-      <li>Append <var>child</var> to <var>children</var>.
-
-      <li>Insert <var>child</var> as the previous sibling of
-      <var>element</var>.
-    </ol>
+    <li>While <var>element</var> has [[children]], insert its first [[child]]
+    into its [[parent]] immediately before it, <span>preserving ranges</span>.
 
     <li>Remove <var>element</var> from its [[parent]].
+    <!-- Notice that a boundary point that was immediately before or after the
+    element will now be immediately before or after its children, just because
+    of the regular range mutation rules, without needing to worry about
+    preserving ranges.  Preserving ranges is only necessary for the sake of
+    boundary points in the element or its descendants. -->
 
     <li>Return <var>children</var>.
   </ol>
@@ -586,11 +579,15 @@
   [[localname]] "span", with the same attributes and [[ownerdocument]] as
   <var>element</var>.
 
-  <li>Append <var>new element</var> to <var>element</var>'s
-  parent as the previous sibling of <var>element</var>.
+  <li>Insert <var>new element</var> into the [[parent]] of <var>element</var>
+  immediately before it.
+  <!-- This means that the boundary point immediately before the element goes
+  before the new element, and after we remove the old element, the boundary
+  point immediately after the old element will be immediately after the new
+  element, because of the regular range mutation rules. -->
 
-  <li>While <var>element</var> has children, append its first child
-  as the last child of <var>new element</var>.
+  <li>While <var>element</var> has [[children]], append its first [[child]] as
+  the last [[child]] of <var>new element</var>, <span>preserving ranges</span>.
 
   <li>Remove <var>element</var> from its [[parent]].
 
@@ -753,15 +750,21 @@
     is not the [[previoussibling]] of <var>node</var>:
 
     <ol>
-      <li>While <var>candidate</var> has [[children]], append the first
-      [[child]] of <var>candidate</var> as the last [[child]] of
-      <var>candidate</var>'s [[parent]].
+      <li>While <var>candidate</var> has [[children]], insert the first
+      [[child]] of <var>candidate</var> into <var>candidate</var>'s [[parent]]
+      immediately before <var>candidate</var>, <span>preserving ranges</span>.
 
       <li>Insert <var>candidate</var> into <var>node</var>'s [[parent]] before
-      <var>node</var>.
+      <var>node</var>'s [[previoussibling]].  <!-- If candidate had no
+      children, any boundary point inside it will get moved to its parent here,
+      which is okay.  We don't want to preserve ranges, because that would move
+      boundary points that originally were in candidate but were moved to its
+      parent by the last step to move to node's parent.  We move to before the
+      previous sibling so that boundary points before and after the previous
+      sibling wind up before or after candidate. -->
 
-      <li>Append the [[previoussibling]] of <var>candidate</var> as the last
-      [[child]] of <var>candidate</var>.
+      <li>Append the [[nextsibling]] of <var>candidate</var> as the last
+      [[child]] of <var>candidate</var>, <span>preserving ranges</span>.
     </ol>
 
     <li>Let <var>candidate</var> be <var>node</var>'s [[nextsibling]].
@@ -779,15 +782,19 @@
     is not the [[nextsibling]] of <var>node</var>:
 
     <ol>
-      <li>While <var>candidate</var> has [[children]], append the first
-      [[child]] of <var>candidate</var> as the last [[child]] of
-      <var>candidate</var>'s [[parent]].
+      <li>While <var>candidate</var> has [[children]], insert the first
+      [[child]] of <var>candidate</var> into <var>candidate</var>'s [[parent]]
+      immediately before <var>candidate</var>, <span>preserving ranges</span>.
 
       <li>Insert <var>candidate</var> into <var>node</var>'s [[parent]] after
-      <var>node</var>.
+      <var>node</var>. <!-- Thus candidate is between the same boundary points
+      as node's next sibling, not the same as node.  When inserting, the new
+      thing always gets put in the same place as its next sibling, not its
+      previous sibling: a boundary point at the place it's inserted moves
+      before the new node, not after. -->
 
       <li>Append the [[nextsibling]] of <var>candidate</var> as the last
-      [[child]] of <var>candidate</var>.
+      [[child]] of <var>candidate</var>, <span>preserving ranges</span>.
     </ol>
 
     <li>Let <var>previous sibling</var> and <var>next sibling</var> be
@@ -796,7 +803,8 @@
     <li>If <var>previous sibling</var> is a <span>simple styling element</span>
     whose <span>specified style</span> and <span>effective style</span> for
     <var>property</var> are both <var>new value</var>, append <var>node</var>
-    as the last [[child]] of <var>previous sibling</var>.
+    as the last [[child]] of <var>previous sibling</var>, <span>preserving
+    ranges</span>.
 
     <li>If <var>next sibling</var> is a <span>simple styling element</span>
     whose <span>specified style</span> and <span>effective style</span> for
@@ -804,12 +812,13 @@
 
     <ol>
       <li>If <var>node</var> is not a [[child]] of <var>previous sibling</var>,
-      insert <var>node</var> as the first [[child]] of <var>next sibling</var>.
+      insert <var>node</var> as the first [[child]] of <var>next
+      sibling</var>, <span>preserving ranges</span>.
 
       <li>Otherwise, while <var>next sibling</var> has [[children]], append the
       first [[child]] of <var>next sibling</var> as the last [[child]] of
-      <var>previous sibling</var>.  Then remove <var>next sibling</var> from
-      its [[parent]].
+      <var>previous sibling</var>, <span>preserving ranges</span>.  Then remove
+      <var>next sibling</var> from its [[parent]].
     </ol>
   </ol>
 
@@ -911,20 +920,22 @@
   [[ownerdocument]] of <var>node</var>.
 
   <li>Insert <var>new parent</var> in <var>node</var>'s [[parent]] before
-  <var>node</var>.
+  <var>node</var>.  <!-- This preserves boundary points correctly, as usual.
+  -->
 
   <li>If the <span>effective style</span> of <var>property</var> for <var>new
   parent</var> is not <var>new value</var>, set the CSS property
   <var>property</var> of <var>new parent</var> to <var>new value</var>.
 
-  <li>Append <var>node</var> to <var>new parent</var> as its last [[child]].
+  <li>Append <var>node</var> to <var>new parent</var> as its last [[child]],
+  <span>preserving ranges</span>.
 
   <li>If <var>node</var> is an [[element]] and the <span>effective style</span>
   of <var>property</var> for <var>node</var> is not <var>new value</var>:
 
   <ol>
     <li>Insert <var>node</var> into the [[parent]] of <var>new parent</var>
-    before <var>new parent</var>.
+    before <var>new parent</var>, <span>preserving ranges</span>.
 
     <li>Remove <var>new parent</var> from its [[parent]].
 
@@ -1468,12 +1479,8 @@
 state of the [[range]] for this command is then true, <span>style</span> each
 returned [[node]] with <var>property</var> "vertical-align" and <var>new
 value</var> "baseline".  Otherwise, <span>style</span> them with <var>new
-value</var> "baseline", then <span>style</span> them again with <var>new
-value</var> "sub".
-
-<p class=XXX>This doesn't work, because we'll have removed some of the things
-that we want to style in some cases.  Need to define what happens to a range
-when you style its nodes, then re-decompose the same range before re-styling.
+value</var> "baseline", then <span>decompose</span> the [[range]] again and
+<span>style</span> each returned [[node]] with <var>new value</var> "sub".
 
 <dd><strong>State</strong>: True if every [[text]] node that is
 <span>effectively contained</span> in the [[range]] has <span>effective
@@ -1488,8 +1495,8 @@
 state of the [[range]] for this command is then true, <span>style</span> each
 returned [[node]] with <var>property</var> "vertical-align" and <var>new
 value</var> "baseline".  Otherwise, <span>style</span> them with <var>new
-value</var> "baseline", then <span>style</span> them again with <var>new
-value</var> "super".
+value</var> "baseline", then <span>decompose</span> the [[range]] again and
+<span>style</span> each returned [[node]] with <var>new value</var> "super".
 
 <dd><strong>State</strong>: True if every [[text]] node that is
 <span>effectively contained</span> in the [[range]] has <span>effective