Rewrite everything, again
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Tue, 15 Mar 2011 16:00:48 -0600
changeset 14 ed4faa551cc5
parent 13 be24141352c7
child 15 9b6a8f5e5622
Rewrite everything, again

Still making progress!
Makefile
autoimplementation.html
editcommands.html
implementation.html
implementation.js
notes.txt
preprocess
source.html
--- a/Makefile	Wed Mar 02 12:17:54 2011 -0700
+++ b/Makefile	Tue Mar 15 16:00:48 2011 -0600
@@ -1,6 +1,7 @@
 ANOLIS = anolis
 
-all: editcommands.html xrefs.json
+#all: editcommands.html xrefs.json
+all: editcommands.html
 
 intermediate.html: source.html Makefile
 	./preprocess
@@ -9,5 +10,6 @@
 	$(ANOLIS) --output-encoding=ascii --omit-optional-tags --enable=xspecxref \
 	--enable=refs --use-strict $< $@
 
-xrefs.json: intermediate.html Makefile
-	$(ANOLIS) --dump-xrefs $< /tmp/spec
+# Hangs and it's useless for me anyway, kill it
+#xrefs.json: intermediate.html Makefile
+#	$(ANOLIS) --dump-xrefs $< /tmp/spec
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/autoimplementation.html	Tue Mar 15 16:00:48 2011 -0600
@@ -0,0 +1,147 @@
+<!doctype html>
+<table border=1>
+	<tr><th>Input <th>Spec <th>Browser
+</table>
+<script src=../implementation.js></script>				
+<script>
+// Note: this data format can only yield selections whose start and end are
+// inside text nodes.
+var tests = [
+	'foo[bar]baz',
+	'foo]bar[baz',
+	'foo[bar<i>baz]qoz</i>quz',
+	'foo<span style="font-weight: bold">[bar]</span>baz',
+	'foo<b>[bar]</b>baz',
+	'foo[<b>bar</b>]baz',
+	'foo[<b>bar]</b>baz',
+	'foo<b>[bar</b>]baz',
+	'foo<strong>[bar]</strong>baz',
+	'foo[<strong>bar</strong>]baz',
+	'foo[<strong>bar]</strong>baz',
+	'foo<strong>[bar</strong>]baz',
+	'foo<span style="font-weight: bold">[bar]</span>baz',
+	'foo[<span style="font-weight: bold">bar</span>]baz',
+	'foo[<span style="font-weight: bold">bar]</span>baz',
+	'foo<span style="font-weight: bold">[bar</span>]baz',
+	'<b>{<p>foo</p><p>bar</p>}<p>baz</p></b>',
+	'<b><p>foo[<i>bar</i>}</p><p>baz</p></b>',
+	'foo [bar <b>baz] qoz</b> quz sic',
+	'foo bar <b>baz [qoz</b> quz] sic'
+];
+
+var table = document.querySelector("table");
+table.contentEditable = "true";
+for (var i = 0; i < tests.length; i++) {
+	var tr = document.createElement("tr");
+	table.appendChild(tr);
+
+	var inputCell = document.createElement("td");
+	inputCell.textContent = tests[i];
+	tr.appendChild(inputCell);
+
+	var specCell = document.createElement("td");
+	specCell.innerHTML = tests[i];
+	tr.appendChild(specCell);
+	selectBrackets(specCell);
+	myExecCommand("bold");
+	specCell.textContent = specCell.innerHTML;
+
+	var browserCell = document.createElement("td");
+	browserCell.innerHTML = tests[i];
+	tr.appendChild(browserCell);
+	selectBrackets(browserCell);
+	document.execCommand("bold", null, false);
+	browserCell.textContent = browserCell.innerHTML;
+}
+getSelection().removeAllRanges();
+table.contentEditable = false;
+
+function selectBrackets(node) {
+	var startNode, startOffset, endNode, endOffset;
+
+	var cur = node;
+	while (true) {
+		if (!cur || (cur != node && !(cur.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINS))) {
+			break;
+		}
+
+		if (cur.nodeType != Node.TEXT_NODE) {
+			cur = nextNode(cur);
+			continue;
+		}
+
+		var data = cur.data.replace(/\]/g, "");
+		var startIdx = data.indexOf("[");
+
+		data = cur.data.replace(/\[/g, "");
+		var endIdx = data.indexOf("]");
+
+		cur.data = cur.data.replace(/[\[\]]/g, "");
+
+		if (startIdx != -1) {
+			startNode = cur;
+			startOffset = startIdx;
+		}
+
+		if (endIdx != -1) {
+			endNode = cur;
+			endOffset = endIdx;
+		}
+
+		// These are only legal as the first or last
+		data = cur.data.replace(/\}/g, "");
+		var elStartIdx = data.indexOf("{");
+
+		data = cur.data.replace(/\{/g, "");
+		var elEndIdx = cur.data.indexOf("}");
+
+		if (elStartIdx == 0) {
+			startNode = cur.parentNode;
+			startOffset = getNodeIndex(cur);
+		} else if (elStartIdx != -1) {
+			startNode = cur.parentNode;
+			startOffset = getNodeIndex(cur) + 1;
+		}
+		if (elEndIdx == 0) {
+			endNode = cur.parentNode;
+			endOffset = getNodeIndex(cur);
+		} else if (elEndIdx != -1) {
+			endNode = cur.parentNode;
+			endOffset = getNodeIndex(cur) + 1;
+		}
+
+		cur.data = cur.data.replace(/[{}]/g, "");
+		if (!cur.data.length) {
+			if (cur == startNode || cur == endNode) {
+				throw "You put a square bracket where there was no text node . . .";
+			}
+			var oldCur = cur;
+			cur = nextNode(cur);
+			oldCur.parentNode.removeChild(oldCur);
+		} else {
+			cur = nextNode(cur);
+		}
+	}
+
+	if ("extend" in getSelection()) {
+		// WebKit behaves unreasonably for collapse(), so do that manually.
+		var range = document.createRange();
+		range.setStart(startNode, startOffset);
+		getSelection().removeAllRanges();
+		getSelection().addRange(range);
+		getSelection().extend(endNode, endOffset);
+	} else {
+		// IE9.  Selections have no direction, so we just make the selection
+		// always forwards;
+		var range = document.createRange();
+		range.setStart(startNode, startOffset);
+		range.setEnd(endNode, endOffset);
+		if (range.collapsed) {
+			// Phooey, we got them backwards.
+			range.setEnd(startNode, startOffset);
+		}
+		getSelection().removeAllRanges();
+		getSelection().addRange(range);
+	}
+}
+</script>
--- a/editcommands.html	Wed Mar 02 12:17:54 2011 -0700
+++ b/editcommands.html	Tue Mar 15 16:00:48 2011 -0600
@@ -2,7 +2,7 @@
 <title>HTML Editing Commands</title>
 <link href=http://www.whatwg.org/style/specification rel=stylesheet>
 <style>
- pre, code { font-family:monospace, sans-serif; }
+ pre, code, xmp { font-family:monospace, sans-serif; }
  h2 code, h3 code, h4 code,
  h2 :link, h3 :link, h4 :link,
  h2 :visited, h3 :visited, h4 :visited
@@ -15,11 +15,18 @@
      text-transform: uppercase;
    }
  }
+ xmp {
+   font-size: inherit;
+   font-variant: normal;
+   margin-left: 2em;
+   white-space: pre-wrap;
+ }
+ div.note > p:first-child::before { content: 'Note: '; }
 </style>
 <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-2-march-2011>Work in Progress &mdash; Last Update 2 March 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-15-march-2011>Work in Progress &mdash; Last Update 15 March 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;ayg+spec@aryeh.name&gt;
@@ -50,10 +57,14 @@
  <li><a href=#issues><span class=secno>2 </span>Issues</a></li>
  <li><a href=#definitions><span class=secno>3 </span>Definitions</a></li>
  <li><a href=#decomposing-a-range-into-nodes><span class=secno>4 </span>Decomposing a Range into Nodes</a></li>
- <li><a href=#unstyling-an-element><span class=secno>5 </span>Unstyling an element</a></li>
- <li><a href=#styling-a-range><span class=secno>6 </span>Styling a Range</a></li>
- <li><a href=#unstyling-a-range><span class=secno>7 </span>Unstyling a Range</a></li>
- <li><a href=#commands><span class=secno>8 </span>Commands</a></li>
+ <li><a href="#clearing-an-element's-styles"><span class=secno>5 </span>Clearing an element's styles</a></li>
+ <li><a href="#recursively-clearing-an-element's-styles"><span class=secno>6 </span>Recursively clearing an element's styles</a></li>
+ <li><a href=#styling-a-node><span class=secno>7 </span>Styling a Node</a></li>
+ <li><a href=#recursively-styling-a-node><span class=secno>8 </span>Recursively styling a Node</a></li>
+ <li><a href=#styling-a-range><span class=secno>9 </span>Styling a Range</a></li>
+ <li><a href=#unstyling-a-node><span class=secno>10 </span>Unstyling a Node</a></li>
+ <li><a href=#unstyling-a-range><span class=secno>11 </span>Unstyling a Range</a></li>
+ <li><a href=#commands><span class=secno>12 </span>Commands</a></li>
  <li><a class=no-num href=#references>References</a></li>
  <li><a class=no-num href=#acknowledgements>Acknowledgements</a></ol>
 <!--end-toc-->
@@ -75,35 +86,37 @@
 <h2 id=issues><span class=secno>2 </span>Issues</h2>
 
 <ul>
-  <li><p>The styling/unstyling algorithms produce unreasonably messy DOMs.  I
-  don't know how far I'll go to simplify the output, but at the very least, I
-  need to wrap continguous phrasing content in a single tag, and be more
-  aggressive about splitting up existing tags instead of nesting extra tags
-  inside (e.g., unbolding the middle letter of <code title="">&lt;b&gt;abc&lt;/b&gt;</code> should become <code title="">&lt;b&gt;a&lt;/b&gt;b&lt;b&gt;c&lt;/b&gt;</code> instead of <code title="">&lt;b&gt;a&lt;span style=font-weight:normal&gt;b&lt;/span&gt;c&lt;/b&gt;</code>).
-
-  <li><p>Need to make CSS terminology more precise, about setting/unsetting CSS
+  <li>Need to make CSS terminology more precise, about setting/unsetting CSS
   properties.  The intent is to modify the style attribute, CSSOM-style.
   Likewise, CSS value comparisons need to be done after serializing both
   values, so "bold" == "700" and "red" == "#f00" and so on.
 
-  <li><p>Also not sure about computed style.  There are differences between
+  <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><p>The wording I use for DOM stuff is a bit of a mess, often either
+  <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><p>I haven't put any thought yet into collapsed ranges or selections.
+  <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><p>I also don't pay attention to what happens to the selection when you
+  <li>I also don't pay attention to what happens to the selection when you
   mutate the DOM.  This is essential.
 
-  <li><p>JavaScript can modify the DOM synchronously in some cases, such as DOM
+  <li>JavaScript can modify the DOM synchronously in some cases, such as DOM
   mutation events and onunload when moving around iframes and objects.  This
   has to be dealt with somehow.  (Pointed out by Ryosuke Niwa of WebKit: <a href=http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2011-March/030730.html>1</a>
   <a href=http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2011-March/030751.html>2</a>)
+
+  <li>I'm sloppy about handling things like nodes that don't descend from a
+  Document, comments that are children of a Document, that sort of thing.  Not
+  essential for prototyping, but needs to be cleaned up eventually.
+
+  <li>I don't pay attention to whether designMode/contenteditable is actually
+  set.  I should be doing things like not doing anything if the selection isn't
+  editable, making sure not to break out of contenteditable regions, etc.
 </ul>
 
 
@@ -114,65 +127,43 @@
 <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attr-namespace title=concept-attr-namespace>namespace</a> is
 the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#html-namespace>HTML namespace</a>.
 
-<p>The <dfn id=first-node>first node</dfn> of a <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> is 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> returned by the
-following algorithm:
+<p>The <dfn id=beginning-element>beginning element</dfn> of a <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> is returned by the following
+algorithm:
 
 <ol>
-  <li><p>Let <var title="">range</var> be the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> under discussion.
-
-  <li><p>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-offset title=concept-boundary-point-offset>offset</a> is equal 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 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>, return the first
-  <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> that is after the <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 all its
-  <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> (if any) 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 there is no such <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a>,
-  return the last <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> in the document.
-
-  <li><p>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> has children,
-  return the child whose <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> is equal to <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-offset title=concept-boundary-point-offset>offset</a>.
-
-  <li><p>Return <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>.
-</ol>
-
-<p>The <dfn id=last-node>last node</dfn> of a <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> is 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> returned by the
-following algorithm:
+  <li>If the <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> of the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></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>,
+  <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment><code class=external data-anolis-spec=domcore>Comment</code></a>, or <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction><code class=external data-anolis-spec=domcore>ProcessingInstruction</code></a> node, and the <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> of the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> is not equal 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 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>, let <var title="">first node</var> be the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></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>.
 
-<ol>
-  <li><p>Let <var title="">range</var> be the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> under discussion.
-
-  <li><p>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-offset title=concept-boundary-point-offset>offset</a> is zero, return the
-  last <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> that is before the <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> 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 there is no such <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a>, return the first <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> in the document.
+  <li>Otherwise, let <var title="">first node</var> be the first <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> 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> that is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>, if there is any.
 
-  <li><p>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> has children,
-  return the child whose <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> is equal to <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-offset title=concept-boundary-point-offset>offset</a> minus one.
-
-  <li><p>Return <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>.
-</ol>
+  <li>If <var title="">first node</var> is defined and 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>, return
+  <var title="">first node</var>.
 
-<p>The <dfn id=beginning-element>beginning element</dfn> of a <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> is its <a href=#first-node>first
-node</a> if that 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>; or the parent of its <a href=#first-node>first
-node</a>, if <em>that</em> 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>; or else null.
+  <li>Otherwise, if <var title="">first node</var> is defined and 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> 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>, return <var title="">first 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>.
 
-<p class=XXX>(It will be null only in weird cases, like selecting a comment
-whose parent is a document, or the child of a document fragment, or whatever.
-I'm ignoring those cases for now.)
+  <li>Return null.
+</ol>
 
 <p>The <dfn id=active-range>active range</dfn> of a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#document><code class=external data-anolis-spec=domcore>Document</code></a> is the value returned by the
 following algorithm:
 
 <ol>
-  <li><p>Let <var title="">selection</var> be the result of calling <a href=http://html5.org/specs/dom-range.html#dom-document-getselection><code class=external data-anolis-spec=domrange title=dom-Document-getSelection>getSelection()</code></a> on
+  <li>Let <var title="">selection</var> be the result of calling <a href=http://html5.org/specs/dom-range.html#dom-document-getselection><code class=external data-anolis-spec=domrange title=dom-Document-getSelection>getSelection()</code></a> on
   the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#document><code class=external data-anolis-spec=domcore>Document</code></a>.
 
-  <li><p>If there are no <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>s associated with <var title="">selection</var>,
+  <li>If there are no <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>s associated with <var title="">selection</var>,
   return null.
 
-  <li><p>Let <var title="">start</var> be the <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> with the earliest
+  <li>Let <var title="">start</var> be the <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> with the earliest
   <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-bp-position title=concept-bp-position>position</a> among all of <var title="">selection</var>'s <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>s'
   <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>starts</a>.
 
-  <li><p>Return the last <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> in <var title="">selection</var> 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>
+  <li>Return the last <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> in <var title="">selection</var> 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>
   is <var title="">start</var>.
   <!-- This is what Firefox seems to do, no reason to change it . . . -->
 
@@ -180,325 +171,517 @@
   <a href=http://html5.org/specs/dom-range.html#selection><code class=external data-anolis-spec=domrange>Selection</code></a>, the active range is simply the only one in the selection.
 </ol>
 
+<p>Given a CSS property name <var title="">property name</var>, an (optional) value
+<var title="">property value</var> for that property, and a possibly empty list of
+strings <var title="">tag list</var>, 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> is a <dfn id=potentially-relevant-styling-element>potentially relevant styling
+element</dfn> if it is an <a href=#html-element>HTML element</a> and one of the following
+holds:
+
+<ul>
+  <li>Its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> is in <var title="">tag list</var> and it has no attributes.
+
+  <li>Its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> is in <var title="">tag list</var> or is "span" or is "font",
+  and it has exactly one attribute, and that attribute is an <a href=#html-attribute>HTML
+  attribute</a> with <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attr-local-name title=concept-attr-local-name>local name</a> "style", and that attribute sets
+  exactly one CSS property, and that property is <var title="">property name</var>, and
+  either <var title="">property value</var> is undefined or the value the attribute sets
+  the property to is <var title="">property value</var>.
+
+  <li>Its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> is "font", and it has exactly one attribute, and that
+  attribute is a <a href=http://www.whatwg.org/html/#dom-font-color><code class=external data-anolis-spec=html title=dom-font-color>color</code></a>
+  attribute, and either <var title="">property value</var> is undefined or the effect of
+  the attribute is to hint that the CSS color attribute be set to <var title="">property
+  value</var>, and <var title="">property name</var> is "color".
+
+  <li>Its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> is "font", and it has exactly one attribute, and that
+  attribute is a <a href=http://www.whatwg.org/html/#dom-font-face><code class=external data-anolis-spec=html title=dom-font-face>face</code></a>
+  attribute, and either <var title="">property value</var> is undefined or the effect of
+  the attribute is to hint that the CSS font-family attribute be set to
+  <var title="">property value</var>, and <var title="">property name</var> is "font-family".
+
+  <li>Its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> is "font", and it has exactly one attribute, and that
+  attribute is a <a href=http://www.whatwg.org/html/#dom-font-size><code class=external data-anolis-spec=html title=dom-font-size>size</code></a>
+  attribute, and either <var title="">property value</var> is undefined or the effect of
+  the attribute is to hint that the CSS font-size attribute be set to
+  <var title="">property value</var>, and <var title="">property name</var> is "font-size".
+
+  <!-- Should we bother handling <font color=red style=color:red>?  Let's not.
+  -->
+</ul>
+
+<p>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> is a <dfn id=relevant-styling-element>relevant styling element</dfn> if it is a
+<a href=#potentially-relevant-styling-element>potentially relevant styling element</a>, and its CSS property
+<var title="">property name</var> computes to <var title="">property value</var> (which cannot be
+undefined).
+
+<div class=note>
+<p>If <var title="">property name</var> is "font-weight", <var title="">property value</var> is
+"bold", and <var title="">tag list</var> contains "b", an example of a <a href=#potentially-relevant-styling-element>potentially
+relevant styling element</a> that is not actually <a href=#relevant-styling-element title="relevant
+styling element">relevant</a> is
+
+</p><xmp><p style="font-weight: 100"><b>Foo</b></p></xmp>
+
+<p>Since <a href=http://www.whatwg.org/html/#the-b-element><code class=external data-anolis-spec=html title="the b element">b</code></a>'s default
+font-weight is "bolder", the computed font-weight will most likely end up being
+"normal" or lighter.
+</div>
+
+<p>A <dfn id=phrasing-element>phrasing element</dfn> is either an <a href=#html-element>HTML element</a> that is
+categorized as <a class=external data-anolis-spec=html href=http://www.whatwg.org/html/#phrasing-content>phrasing content</a>, or a <a class=external data-anolis-spec=html href=http://www.whatwg.org/html/#non-conforming-element title="non-conforming element">non-conforming</a> <a href=#html-element>HTML element</a>
+(which thus has no categories), or 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> that is not an <a href=#html-element>HTML
+element</a>.
+
+<p class=XXX>We should allow unrecognized HTML elements too.
+
+<p>The <dfn id=specified-style>specified style</dfn> of 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> for a given <var title="">property
+name</var> is returned by the following algorithm, which will return either a
+CSS value or null:
+
+<ol>
+  <li>If the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> has a <a href=http://www.whatwg.org/html/#the-style-attribute><code class=external data-anolis-spec=html title="the style attribute">style</code></a> attribute set, and that attribute has
+  the effect of setting <var title="">property name</var>, return the value that it sets
+  <var title="">property name</var> to.
+
+  <li>If the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> is a <a href=http://www.whatwg.org/html/#font><code class=external data-anolis-spec=html>font</code></a> element
+  that has an attribute whose effect is to create a <a class=external data-anolis-spec=html href=http://www.whatwg.org/html/#presentational-hints title="presentational hints">presentational hint</a> for
+  <var title="">property name</var>, return the value that the hint sets <var title="">property
+  name</var> to.
+
+  <li>If the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> is in the following list, and <var title="">property name</var>
+  is equal to the CSS property name listed for it, return the string listed for
+  it.
+
+  <p class=XXX>Add any other elements that can be output by the style/unstyle
+  algorithms, or existing browser implementations of execCommand().
+
+  <dl class=switch>
+    <dt><a href=http://www.whatwg.org/html/#the-b-element><code class=external data-anolis-spec=html title="the b element">b</code></a>
+    <dt><a href=http://www.whatwg.org/html/#the-strong-element><code class=external data-anolis-spec=html title="the strong element">strong</code></a>
+    <dd>font-weight: "bold"
+
+    <dt><a href=http://www.whatwg.org/html/#the-i-element><code class=external data-anolis-spec=html title="the i element">i</code></a>
+    <dt><a href=http://www.whatwg.org/html/#the-em-element><code class=external data-anolis-spec=html title="the em element">em</code></a>
+    <dd>font-style: "italic"
+
+    <dt><a href=http://www.whatwg.org/html/#the-u-element><code class=external data-anolis-spec=html title="the u element">u</code></a>
+    <dd>text-decoration: "underline"
+  </dl>
+
+  <li>Return null.
+</ol>
+
 <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 href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a>s 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=decomposing-a-range-into-nodes><span class=secno>4 </span>Decomposing a Range into Nodes</h2>
-<p>When a user agent is to <dfn id=decompose-a-range>decompose a <code class=external data-anolis-spec=domrange>Range</code></dfn> <var title="">range</var>, it must run the following steps.
-
-<p class=note>The algorithm returns a 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 in the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> that
-are not contained in any other <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> in the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>.  It splits <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a>
-nodes if necessary, but isn't picky about <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment><code class=external data-anolis-spec=domcore>Comment</code></a>s or
-<a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction><code class=external data-anolis-spec=domcore>ProcessingInstruction</code></a>s.
+<p>When a user agent is to <dfn id=decompose-a-range>decompose a <code class=external data-anolis-spec=domrange>Range</code></dfn> <var title="">range</var>,
+it must run the following steps.
 
 <ol>
-  <li><p>Let <var title="">start node</var>, <var title="">start offset</var>, <var title="">end node</var>, and <var title="">end offset</var> be the <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> of <var title="">range</var>,
-  respectively.
-
-  <li><p>If <var title="">start node</var> or <var title="">end node</var> is not 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>, <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a>, <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction><code class=external data-anolis-spec=domcore>ProcessingInstruction</code></a>, or <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment><code class=external data-anolis-spec=domcore>Comment</code></a> node, or is
-  not 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 has no parent, abort these steps.
-
-  <p class=XXX>Figure out something sensible here.
+  <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><p>If <var title="">start node</var> and <var title="">end node</var> are both
-  <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> nodes, and <var title="">start node</var> is the same as <var title="">end
-  node</var>, and neither <var title="">start offset</var> nor <var title="">end
-  offset</var> is equal to 0 or 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>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><p>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="">start offset</var>)</code></a> on
-    <var title="">start node</var> and set <var title="">start node</var> to the
-    result.
+    <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><p>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> &minus; <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><p>Return the list consisting of the single <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="">start
-    node</var>, and abort these steps.
+    <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><p>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>, 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="">start offset</var>)</code></a> on
-  <var title="">start node</var> and set <var title="">start node</var> to the
-  returned node.  Set <var title="">start offset</var> to 0.
-
-  <li><p>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>.
-
-  <li><p>If <var title="">start offset</var> is nonzero and equals 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>, let <var title="">node</var> be the first <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> after
-  <var title="">start node</var> in <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>.  If there is no such <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a>, return
-  the empty list.
-
-  <li><p>Otherwise, if <var title="">start 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> with at least
-  one child, let <var title="">node</var> be the <var title="">start offset</var>th child of
-  <var title="">start node</var>.
-
-  <li><p>Otherwise, let <var title="">node</var> be <var title="">start node</var>.
-
-  <li><p>If <var title="">end 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 <var title="">end
-  offset</var> is not 0, let <var title="">end</var> be the child of <var title="">end 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="">end offset</var> &minus; 1.
-
-  <li><p>Otherwise, if <var title="">end offset</var> is 0, let <var title="">end</var> be the first <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> before <var title="">end node</var> in
-  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>.  If there is no such <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a>, return the empty list.
-
-  <li><p>Otherwise, let <var title="">end</var> be <var title="">end node</var>.
-
-  <!-- We try to include a node's parent instead of that node if possible,
-  because this generally reduces the number of nodes we're handling.  So if the
-  string "oo bar" was selected in <b>Foo <i>bar</i></b>, we'd add the <i> to
-  the selection, even if the browser registered the end as the text node "bar".
-  -->
-  <li><p>While <var title="">node</var> is the first child of its parent and <var title="">end</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-descendant title=concept-tree-descendant>descendant</a> of <var title="">node</var>'s parent,
-  set <var title="">node</var> to <var title="">node</var>'s parent.
-
-  <li><p>While <var title="">end</var> is the last child of its parent and <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-descendant title=concept-tree-descendant>descendant</a> of <var title="">end</var>'s parent,
-  set <var title="">end</var> to <var title="">node</var>'s parent.
-
-  <li><p>Let <var title="">node list</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><p>While <var title="">node</var> is not after <var title="">end</var> in
-  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>:
+  <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>:
 
   <ol>
-    <li><p>Append <var title="">node</var> to <var title="">node list</var>.
+    <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><p>Set <var title="">node</var> to the first <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> 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> that is
-    after <var title="">node</var> and all its <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> (if any).  If no such
-    <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> exists, break out of these substeps.
-
-    <li><p>While <var title="">node</var> is 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="">end</var>, set <var title="">node</var> to <var title="">node</var>'s first child.
+    <li>Set <var title="">start offset</var> to 0.
   </ol>
 
-  <li><p>Return <var title="">node list</var>.
+  <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>Return a list consisting of every <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in
+  <var title="">range</var> in <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>, omitting any whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is also
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">range</var>.
 </ol>
 
 
-<h2 id=unstyling-an-element><span class=secno>5 </span>Unstyling an element</h2>
-<p>When a user agent is to <dfn id=unstyle-an-element>unstyle an element</dfn>, it must run the
-following steps.
+<h2 id="clearing-an-element's-styles"><span class=secno>5 </span>Clearing an element's styles</h2>
+<p>When a user agent is to <dfn id=clear-styles>clear styles</dfn> on an element, it must run
+the following steps:
+
+<div class=note>
+<p>Clearing styles (<a href=#recursively-clear-styles title="recursively clear styles">recursively</a>
+or not) can remove it from its parent and put other nodes in its place.  When
+implementations do something like clear style on all children of an element,
+they should take care not to assume that the set of children won't change as
+they're unstyled.  If the element is removed, the algorithm will return the
+list of nodes inserted in its place.
+
+<p>Clearing styles only removes inline styles from the element.  It doesn't
+ensure that the element isn't inheriting styles from an ancestor (or a style
+rule).  For that, one must <a href=#unstyle-a-node>unstyle a node</a>.
+</div>
 
 <ol>
-  <li><p>Let <var title="">element</var> be the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> to be unstyled.
+  <li>Let <var title="">element</var> be the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> to be unstyled.
 
-  <li><p>Let <var title="">property name</var> and <var title="">tag list</var> be as
+  <li>Let <var title="">property name</var> and <var title="">tag list</var> be as
   in the invoking algorithm.
 
-  <li><p>Let <var title="">element children</var> be the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> children of
-  <var title="">element</var>.
-
-  <li><p><a href=#unstyle-an-element title="unstyle an element">Unstyle</a> each <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> in
-  <var title="">element children</var>, in order.
-
-  <p class=note>Unstyling an element can change the number of children its
-  parent has, so the list of children to unstyle needs to be computed
-  beforehand.
-
-  <li><p>If either
-
-  <ul>
-    <li><p><var title="">element</var> is an <a href=#html-element>HTML element</a> with
-    <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> either "span" or in <var title="">tag list</var>, and it has
-    only a single attribute, and that attribute is named "style", and
-    that style attribute sets only the CSS property <var title="">property
-    name</var>; or
-
-    <li><p><var title="">element</var> is an <a href=#html-element>HTML element</a> with
-    <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> in <var title="">tag list</var> and it has no attributes,
-  </ul>
-
-  <p>then:
+  <li>If <var title="">element</var> is a <a href=#potentially-relevant-styling-element>potentially relevant styling
+  element</a>:
 
   <ol>
-    <li><p>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>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><p>While <var title="">element</var> has children:
+    <li>While <var title="">element</var> has children:
 
     <ol>
-      <li><p>Let <var title="">child</var> be the first child of <var title="">element</var>.
+      <li>Let <var title="">child</var> be the first child of <var title="">element</var>.
 
-      <li><p>Append <var title="">child</var> to <var title="">children</var>.
+      <li>Append <var title="">child</var> to <var title="">children</var>.
 
-      <li><p>Insert <var title="">child</var> as the previous sibling of
+      <li>Insert <var title="">child</var> as the previous sibling of
       <var title="">element</var>.
     </ol>
 
-    <li><p>Remove <var title="">element</var>.
+    <li>Remove <var title="">element</var>.
 
-    <li><p>Return <var title="">children</var>.
+    <li>Return <var title="">children</var>.
   </ol>
 
-  <li><p>Unset the CSS property <var title="">property name</var> of <var title="">element</var>.
+  <li>Unset the CSS property <var title="">property name</var> of <var title="">element</var>.
 
-  <li><p>If <var title="">element</var> is not an <a href=#html-element>HTML element</a> or its
+  <li>If <var title="">element</var> is a <a href=http://www.whatwg.org/html/#font><code class=external data-anolis-spec=html>font</code></a>
+  element:
+
+  <ol>
+    <li>If <var title="">property name</var> is "color", unset <var title="">element</var>'s
+    <a href=http://www.whatwg.org/html/#dom-font-color><code class=external data-anolis-spec=html title=dom-font-color>color</code></a> attribute, if
+    set.
+
+    <li>If <var title="">property name</var> is "font-family", unset
+    <var title="">element</var>'s <a href=http://www.whatwg.org/html/#dom-font-face><code class=external data-anolis-spec=html title=dom-font-face>face</code></a> attribute, if set.
+
+    <li>If <var title="">property name</var> is "font-size", unset <var title="">element</var>'s
+    <a href=http://www.whatwg.org/html/#dom-font-size><code class=external data-anolis-spec=html title=dom-font-size>size</code></a> attribute, if
+    set.
+  </ol>
+
+  <li>If <var title="">element</var> is not an <a href=#html-element>HTML element</a> or its
   <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> is not in <var title="">tag list</var>, return the empty list.
+  <!-- If we get past this step, we're something like <b class=foo> where we
+  want to keep the extra attributes, so we stick them on a span. -->
 
-  <li><p>Let <var title="">new element</var> be a new <a href=#html-element>HTML element</a> with
+  <li>Let <var title="">new element</var> be a new <a href=#html-element>HTML element</a> with
   <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> "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><p>Append <var title="">new element</var> to <var title="">element</var>'s
+  <li>Append <var title="">new element</var> to <var title="">element</var>'s
   parent as the previous sibling of <var title="">element</var>.
 
-  <li><p>While <var title="">element</var> has children, append its first child
+  <li>While <var title="">element</var> has children, append its first child
   as the last child of <var title="">new element</var>.
 
-  <li><p>Remove <var title="">element</var>.
+  <li>Remove <var title="">element</var>.
 
-  <li><p>Return the one-<a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> list consisting of <var title="">new element</var>.
+  <li>Return the one-<a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> list consisting of <var title="">new element</var>.
 </ol>
 
 
-<h2 id=styling-a-range><span class=secno>6 </span>Styling a Range</h2>
-<p>When a user agent is to <dfn id=style-a-range>style a <code class=external data-anolis-spec=domrange>Range</code></dfn> <var title="">range</var>, it
+<h2 id="recursively-clearing-an-element's-styles"><span class=secno>6 </span>Recursively clearing an element's styles</h2>
+<p>When a user agent is to <dfn id=recursively-clear-styles>recursively clear styles</dfn> on an element,
+it must run the following steps:
+
+<ol>
+  <li>Let <var title="">element</var> be the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> to be unstyled.
+
+  <li>Let <var title="">property name</var> and <var title="">tag list</var> be as
+  in the invoking algorithm.
+
+  <li>Let <var title="">element children</var> be the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> children of
+  <var title="">element</var>.
+
+  <li><a href=#recursively-clear-styles>Recursively clear styles</a> on each <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> in <var title="">element
+  children</var>.
+
+  <li><a href=#clear-styles>Clear styles</a> on <var title="">element</var>, and return the resulting
+  list.
+</ol>
+
+
+<h2 id=styling-a-node><span class=secno>7 </span>Styling a Node</h2>
+<p>When a user agent is to <dfn id=style-a-node>style a <code class=external data-anolis-spec=domcore>Node</code></dfn> <var title="">node</var>, it must
+run the following steps.  There are three inputs: a CSS property name
+<var title="">property name</var>, a new value <var title="">property value</var>, and a possibly
+empty list of strings <var title="">tag list</var>.
+
+<p class=note>This algorithm applies the given style to the node itself, but
+doesn't interfere with conflicting styles on its descendants.  <a href=#recursively-style-a-node title="recursively style a node">Recursive styling</a> removes conflicting
+styles from descendants first.
+
+<ol>
+  <li>If <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is null, or if <var title="">node</var> is not 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>, <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a>, <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment><code class=external data-anolis-spec=domcore>Comment</code></a>, or <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction><code class=external data-anolis-spec=domcore>ProcessingInstruction</code></a> node, abort
+  this algorithm. <!-- XXX: What to do here? -->
+
+  <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>:
+
+  <ol>
+    <li><a href=#clear-styles>Clear styles</a> on <var title="">node</var>, and let <var title="">new
+    nodes</var> be the result.
+
+    <li>For each <var title="">new node</var> in <var title="">new nodes</var>, <a href=#style-a-node title="style a node">style <var title="">new node</var></a>, with the same inputs
+    as this invocation of the algorithm.
+
+    <li>If <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is null, abort this algorithm.
+  </ol>
+
+  <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> but not a <a href=#phrasing-element>phrasing
+  element</a>:
+
+  <ol>
+    <li>Let <var title="">children</var> be all <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a> of <var title="">node</var>,
+    omitting any that are <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a>s whose <a href=#specified-style>specified style</a> for
+    <var title="">property name</var> is neither null nor equal to <var title="">property
+    value</var>.
+
+    <li><a href=#style-a-node title="style a node">Style</a> each <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> in
+    <var title="">children</var>.
+
+    <li>Abort this algorithm.
+  </ol>
+
+  <li>If <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> is a <a href=#relevant-styling-element>relevant styling
+  element</a>, 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 its
+  <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> and abort this algorithm.
+
+  <li>If <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> is a <a href=#relevant-styling-element>relevant styling
+  element</a>, 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 its
+  <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> and abort this algorithm.
+
+  <li>If <var title="">node</var> is a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment><code class=external data-anolis-spec=domcore>Comment</code></a> or <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction><code class=external data-anolis-spec=domcore>ProcessingInstruction</code></a>, abort
+  this algorithm.  <!-- There's no point in making a new element in this case.
+  -->
+
+  <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 computed style of
+  <var title="">property name</var> for it is <var title="">property value</var>, abort this
+  algorithm.
+
+  <li>If <var title="">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 the computed style of
+  <var title="">property name</var> for 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> is <var title="">property value</var>,
+  abort this algorithm.
+
+  <li>Let <var title="">tag</var> be the first string in <var title="">tag list</var>, if that is
+  not empty, or "span" if it is empty.
+
+  <li>Let <var title="">new parent</var> be the result of calling <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement><code class=external data-anolis-spec=domcore title=dom-Document-createElement>createElement(<var title="">tag</var>)</code></a> on the
+  <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>.
+
+  <li>If the computed value of <var title="">property name</var> for <var title="">new
+  parent</var> is not <var title="">property value</var>, set the CSS property
+  <var title="">property name</var> of <var title="">new parent</var> to <var title="">property
+  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>.
+</ol>
+
+
+<h2 id=recursively-styling-a-node><span class=secno>8 </span>Recursively styling a Node</h2>
+<p>When a user agent is to <dfn id=recursively-style-a-node>recursively style a <code class=external data-anolis-spec=domcore>Node</code></dfn>
+<var title="">node</var>, it must run the following steps.  There are three inputs: a
+CSS property name <var title="">property name</var>, a new value <var title="">property
+value</var>, and a possibly empty list of strings <var title="">tag list</var>.
+
+<ol>
+  <li>If <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is null, or if <var title="">node</var> is not 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>, <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a>, <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment><code class=external data-anolis-spec=domcore>Comment</code></a>, or <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction><code class=external data-anolis-spec=domcore>ProcessingInstruction</code></a> node, abort
+  this algorithm. <!-- XXX: What to do here? -->
+
+  <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>:
+
+  <ol>
+    <li><a href=#recursively-clear-styles>Recursively clear styles</a> on <var title="">node</var>, and let
+    <var title="">new nodes</var> be the result.
+
+    <li>For each <var title="">new node</var> in <var title="">new nodes</var>, <a href=#recursively-style-a-node title="recursively style a node">recursively style <var title="">new
+    node</var></a>, with the same inputs as this invocation of the
+    algorithm.
+
+    <li>If <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is null, abort this algorithm.
+  </ol>
+
+  <li><a href=#style-a-node title="style a node">Style</a> <var title="">node</var>.
+</ol>
+
+
+<h2 id=styling-a-range><span class=secno>9 </span>Styling a Range</h2>
+<p>When a user agent is to <dfn id=style-a-range>style a <code class=external data-anolis-spec=domrange>Range</code></dfn>, it must <a href=#decompose-a-range title="decompose a range">decompose</a> the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>, then <a href=#recursively-style-a-node title="recursively style a node">recursively style</a> each <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> in the
+returned list.
+
+
+<h2 id=unstyling-a-node><span class=secno>10 </span>Unstyling a Node</h2>
+<p>When a user agent is to <dfn id=unstyle-a-node>unstyle a <code class=external data-anolis-spec=domcore>Node</code></dfn> <var title="">node</var>, it
 must run the following steps.  There are three inputs: a CSS property name
-<var title="">property name</var>, a new value <var title="">property value</var>, and a possibly
+<var title="">property name</var>, a new value <var title="">new value</var>, and a possibly
 empty list of strings <var title="">tag list</var>.
 
 <ol>
-  <li><p>Let <var title="">node list</var> be the result of <a href=#decompose-a-range title="decompose
-  a range">decomposing</a> <var title="">range</var>.
+  <li>If <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is null, or if <var title="">node</var> is not 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> or <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, abort this algorithm. <!-- XXX: What to do
+  here?  We want to ignore comments and PIs, but we might want to support
+  detached elements, documents, document fragments, . . . -->
 
-  <li><p>For each <var title="">node</var> in <var title="">node list</var>, in order:
+  <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>:
 
   <ol>
-    <li><p>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>:
-
-    <ol>
-      <li><p>Unset the CSS property <var title="">property name</var> of <var title="">node</var>.
+    <li><a href=#recursively-clear-styles>Recursively clear styles</a> on <var title="">node</var>, and let
+    <var title="">new nodes</var> be the result.
 
-      <li><p>If the computed value of <var title="">property name</var> for
-      <var title="">node</var> is not <var title="">property value</var>, set the CSS property
-      <var title="">property name</var> of <var title="">node</var> to <var title="">property value</var>.
-      <!-- This means we don't bother applying the property if the style is
-      already present, e.g., from an ancestor.  But we do apply it if the
-      element is the expected sort of element but the style is wrong anyway,
-      e.g., <span style=font-weight:100><b>Foo</b></span> where b's style is
-      font-weight: bold. -->
+    <li>For each <var title="">new node</var> in <var title="">new nodes</var>, <a href=#unstyle-a-node title="unstyle a node">unstyle <var title="">new node</var></a>, with the same
+    inputs as this invocation of the algorithm.
 
-      <li><p>Let <var title="">element children</var> be the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> children
-      of <var title="">node</var>.
-
-      <li><p><a href=#unstyle-an-element title="unstyle an element">Unstyle</a> each <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a>
-      in <var title="">element children</var>, in order.
+    <li>If <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is null, abort this algorithm.
+  </ol>
 
-      <p class=note>Unstyling an element can change the number of children its
-      parent has, so the list of children to unstyle needs to be computed
-      beforehand.
-    </ol>
+  <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>, let <var title="">current value</var> equal
+  the computed value of <var title="">property name</var> on <var title="">node</var>.
+  Otherwise, let <var title="">current value</var> equal the computed value of
+  <var title="">property name</var> on <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>.
 
-    <li><p>Otherwise, if <var title="">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:
+  <li>If <var title="">current value</var> equals <var title="">new value</var>, abort this
+  algorithm.
+
+  <li>Let <var title="">ancestor list</var> be a 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, initially empty.
+
+  <li>Let <var title="">current ancestor</var> equal <var title="">node</var>.
+
+  <li>While <var title="">current ancestor</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is 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>, set
+  <var title="">current ancestor</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>, then append it to
+  <var title="">ancestor list</var>.
+  
+  <li>While <var title="">ancestor list</var> is not empty, and the last member of
+  <var title="">ancestor list</var> has <a href=#specified-style>specified style</a> for <var title="">property
+  name</var> equal to <var title="">new value</var> or null, remove the last member from
+  <var title="">ancestor list</var>.
+
+  <li>While <var title="">ancestor list</var> is not empty:
+
+  <ol>
+    <li>Let <var title="">current ancestor</var> be the last member of <var title="">ancestor
+    list</var>.
+
+    <li>Remove the last member from <var title="">ancestor list</var>.
+
+    <li>Let <var title="">propagated value</var> be the <a href=#specified-style>specified style</a> of
+    <var title="">current ancestor</var> for <var title="">property name</var>.
+
+    <li>If <var title="">propagated value</var> is null, continue this loop from the
+    beginning.
+
+    <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="">current
+    ancestor</var>.
+
+    <li><a href=#clear-styles>Clear styles</a> on <var title="">current ancestor</var>.
+
+    <li>For every <var title="">child</var> in <var title="">children</var>:
 
     <ol>
-      <li><p>Let <var title="">tag</var> be the first string in <var title="">tag list</var>, or
-      "span" if <var title="">tag list</var> is empty.
-
-      <li><p>Let <var title="">new parent</var> be the result of calling <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement><code class=external data-anolis-spec=domcore title=dom-Document-createElement>createElement(<var title="">tag</var>)</code></a> on
-      the <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><p>Append <var title="">new parent</var> to <var title="">node</var>'s parent as the
-      previous sibling of <var title="">node</var>.
-
-      <li><p>If the computed value of <var title="">property name</var> on <var title="">new
-      parent</var> is not equal to <var title="">property value</var>, set the CSS
-      property <var title="">property name</var> of <var title="">new parent</var> to
-      <var title="">property value</var>.
-      <!-- This is needed if tag list is empty, but also if the correct style
-      is being suppressed for some reason, like <span
-      style=font-weight:100><b>Foo</b></span> where b is font-weight: bolder.
-      -->
-
-      <li><p>Append <var title="">node</var> to <var title="">new parent</var> as
-      its last child.
-    </ol>
+      <li>If <var title="">child</var> is <var title="">node</var>, continue with the next
+      <var title="">child</var>.
 
-    <li><p>Otherwise, do nothing.
-  </ol>
-</ol>
-<!-- Out of IE9, Gecko, WebKit, and Opera, when asked to (e.g.) bold an
-element, IE9 and WebKit and Opera wrap various descendants in <b> or <strong>;
-Gecko just adds a style attribute.  The latter is simpler, particularly because
-you then don't have to worry about making sure you only insert your tags in a
-valid place (which you have to so that text/html serialization is possible, if
-nothing else).  I originally specced the former approach, available in git
-history. -->
+      <li>If <var title="">child</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> whose <a href=#specified-style>specified
+      style</a> for <var title="">property name</var> is neither null nor equal to
+      <var title="">propagated value</var>, continue with the next <var title="">child</var>.
 
+      <li>If <var title="">child</var> is the last member of <var title="">ancestor list</var>,
+      set <var title="">child</var>'s CSS property <var title="">property name</var> to
+      <var title="">propagated value</var> and continue with the next <var title="">child</var>.
 
-<h2 id=unstyling-a-range><span class=secno>7 </span>Unstyling a Range</h2>
-<p>When a user agent is to <dfn id=unstyle-a-range>unstyle a <code class=external data-anolis-spec=domrange>Range</code></dfn> <var title="">range</var>, it
-must run the following steps.  There are three inputs: a CSS property name
-<var title="">property name</var>, a new value <var title="">property value</var>, and a possibly
-empty list of strings <var title="">tag list</var>.
+      <p class=note>This style will be removed on the next loop iteration and
+      distributed to its children.
 
-<ol>
-  <li><p>Let <var title="">node list</var> be the result of <a href=#decompose-a-range title="decompose
-  a range">decomposing</a> <var title="">range</var>.
+      <li><a href=#style-a-node title="style a node">Style</a> <var title="">child</var>, with
+      <var title="">property name</var> and <var title="">tag list</var> as in this algorithm,
+      and <var title="">property value</var> equal to <var title="">propagated value</var>.
+    </ol>
+  </ol>
 
-  <li><p>For each <var title="">node</var> in <var title="">node list</var>, in order:
+  <!-- We might have a rule inherited from someplace where we can't remove it,
+  or maybe even a rule in a stylesheet (although that case is pathological and
+  we generally ignore it) -->
+  <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 <var title="">property name</var> does
+  not compute to <var title="">new value</var> on it, set <var title="">property name</var> to
+  <var title="">new value</var> on it.
+
+  <li>If <var title="">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="">property name</var> does
+  not compute to <var title="">new value</var> on 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><p>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>:
-
-    <ol>
-      <li><p>Let <var title="">extra nodes</var> be the result of <a href=#unstyle-an-element title="unstyle an element">unstyling</a> <var title="">node</var>.
-
-      <li><p>If <var title="">node</var> no longer has a parent:
-
-      <ol>
-        <li><p>Insert all 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>s in <var title="">extra nodes</var> into
-        <var title="">node list</var> immediately after <var title="">node</var>, in order.
-
-        <li><p>Continue with the next <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> in <var title="">node list</var>, if any.
-
-        <p class=note>The next <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> will be the first in <var title="">extra
-        nodes</var>, unless <var title="">extra nodes</var> is empty.
-      </ol>
-
-      <li><p>If the computed value of <var title="">property name</var> for <var title="">node</var> is not <var title="">property value</var>, set the CSS
-      property <var title="">property name</var> of <var title="">node</var> to <var title="">property value</var>.
-
-      <li><p>Let <var title="">element children</var> be the <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> children
-      of <var title="">node</var>.
+    <li>Let <var title="">new parent</var> be the result of calling <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement><code class=external data-anolis-spec=domcore title=dom-Document-createElement>createElement("span")</code></a> on the
+    <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><p><a href=#unstyle-an-element title="unstyle an element">Unstyle</a> each <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a>
-      in <var title="">element children</var>, in order.
-
-      <p class=note>Unstyling an element can change the number of children its
-      parent has, so the list of children to unstyle needs to be computed
-      beforehand.
-    </ol>
-
-    <li><p>Otherwise, if <var title="">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 the computed value of <var title="">property name</var> for <var title="">node</var>'s parent is not <var title="">property value</var>:
+    <li>Set <var title="">property name</var> to <var title="">new value</var> on <var title="">new
+    parent</var>.
 
-    <ol>
-      <li><p>Let <var title="">new parent</var> be the result of calling <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement><code class=external data-anolis-spec=domcore title=dom-Document-createElement>createElement("span")</code></a> on the
-      <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><p>Set the CSS property <var title="">property name</var> of <var title="">new
-      parent</var> to <var title="">property value</var>.
+    <li>Insert <var title="">new parent</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>.
 
-      <li><p>Insert <var title="">new parent</var> as <var title="">node</var>'s
-      previous sibling.
-
-      <li><p>Append <var title="">node</var> to <var title="">new parent</var> as its
-      child.
-    </ol>
-
-    <li><p>Otherwise, do nothing.
+    <li>Append <var title="">node</var> as the last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">new parent</var>.
   </ol>
 </ol>
 
 
-<h2 id=commands><span class=secno>8 </span>Commands</h2>
+<h2 id=unstyling-a-range><span class=secno>11 </span>Unstyling a Range</h2>
+<p>When a user agent is to <dfn id=unstyle-a-range>unstyle a <code class=external data-anolis-spec=domrange>Range</code></dfn> <var title="">range</var>, it
+must <a href=#decompose-a-range title="decompose a range">decompose</a> the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>, then <a href=#unstyle-a-node title="unstyle a node">unstyle</a> each <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> in the returned list.
+
+
+<h2 id=commands><span class=secno>12 </span>Commands</h2>
 <p>The <dfn id=execcommand() title=execCommand()><code>execCommand(<var title="">commandId</var>,
 <var title="">showUI</var>, <var title="">value</var>)</code></dfn> method on the
 <a href=http://www.whatwg.org/html/#htmldocument><code class=external data-anolis-spec=html>HTMLDocument</code></a> interface allows scripts to
@@ -578,17 +761,17 @@
 <dd><p><strong>Value</strong>: The value is given by the following algorithm:
 
 <ol>
-  <li><p>Let <var title="">element</var> be the <a href=#beginning-element>beginning element</a> of the
+  <li>Let <var title="">element</var> be the <a href=#beginning-element>beginning element</a> of the
   <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>.
 
-  <li><p>While the computed style of "background-color" on <var title="">element</var>
+  <li>While the computed style of "background-color" on <var title="">element</var>
   is any fully transparent value, set <var title="">element</var> to its parent.
 
   <p class=XXX>It's intended that for these purposes, the root element will
   have a white background.  Should that be specified somewhere?  I don't think
   all UAs actually do it.
 
-  <li><p>Return the computed style of "background-color" for
+  <li>Return the computed style of "background-color" for
   <var title="">element</var>.
 </ol>
 <!-- Chrome 10 returns rgba(0, 0, 0, 0) if there's no background defined
@@ -625,7 +808,7 @@
 <dd><p><strong>Action</strong>: The user agent must run the following steps:
 
 <ol>
-  <li><p>If <var title="">value</var> is the empty string, abort these steps and do
+  <li>If <var title="">value</var> is the empty string, abort these steps and do
   nothing.
   <!-- This matches Firefox 4b11 and Chrome 11 dev.  IE 9 RC and Opera 11 both
   treat the request literally.  Gecko and WebKit probably have it right here:
@@ -634,35 +817,35 @@
   specify "#" for the value, or the author can rewrite it, so it's not like
   this makes the API less useful. -->
 
-  <li><p>Let <var title="">node list</var> be the result of <a href=#decompose-a-range title="decompose a
+  <li>Let <var title="">node list</var> be the result of <a href=#decompose-a-range title="decompose a
   range">decomposing</a> the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>.
 
-  <li><p>For each <var title="">node</var> in <var title="">node list</var>, in order:
+  <li>For each <var title="">node</var> in <var title="">node list</var>, in order:
 
   <ol>
-    <li><p>Let <var title="">text nodes</var> be a list of all <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
+    <li>Let <var title="">text nodes</var> be a list of all <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
     <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> of <var title="">node</var>, or <var title="">node</var> itself if it's 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.
 
-    <li><p>For each <var title="">text node</var> in <var title="">text nodes</var>, in
+    <li>For each <var title="">text node</var> in <var title="">text nodes</var>, in
     <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>:
 
     <ol>
-      <li><p>Let <var title="">ancestor link</var> be the parent of <var title="">text
+      <li>Let <var title="">ancestor link</var> be the parent of <var title="">text
       node</var>.
 
-      <li><p>While <var title="">ancestor link</var> is not an <a href=#html-element>HTML
+      <li>While <var title="">ancestor link</var> is not an <a href=#html-element>HTML
       element</a>, or its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> is not "a", or it has no <a href=#html-attribute>HTML
       attribute</a> with <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attr-local-name title=concept-attr-local-name>local name</a> "href":
 
       <ol>
-        <li><p>If the parent of <var title="">ancestor link</var> is not 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>,
+        <li>If the parent of <var title="">ancestor link</var> is not 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>,
         set <var title="">ancestor link</var> to null and break from this loop.
 
-        <li><p>Otherwise, set <var title="">ancestor link</var> to its parent.
+        <li>Otherwise, set <var title="">ancestor link</var> to its parent.
       </ol>
 
-      <li><p>If <var title="">ancestor link</var> is not null, set its "href" attribute
+      <li>If <var title="">ancestor link</var> is not null, set its "href" attribute
       to <var title="">value</var> and continue with the next <var title="">text node</var>.
       <!-- There are three approaches here.  For instance, if you ask browsers
       to create a link to "http://example.org" on the "b" here:
@@ -687,16 +870,16 @@
       miss out a character when selecting the link you want to change, do you
       really intend to only change the link of part of it? -->
 
-      <li><p>Let <var title="">new parent</var> be the result of calling <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement><code class=external data-anolis-spec=domcore title=dom-Document-createElement>createElement("a")</code></a> on the
+      <li>Let <var title="">new parent</var> be the result of calling <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement><code class=external data-anolis-spec=domcore title=dom-Document-createElement>createElement("a")</code></a> on the
       <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="">text node</var>.
 
-      <li><p>Call <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-element-setattribute><code class=external data-anolis-spec=domcore title=dom-Element-setAttribute>setAttribute("href",
+      <li>Call <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-element-setattribute><code class=external data-anolis-spec=domcore title=dom-Element-setAttribute>setAttribute("href",
       <var title="">value</var>)</code></a> on <var title="">new parent</var>.
 
-      <li><p>Insert <var title="">new parent</var> into <var title="">text node</var>'s parent as
+      <li>Insert <var title="">new parent</var> into <var title="">text node</var>'s parent as
       the previous sibling of <var title="">text node</var>.
 
-      <li><p>Append <var title="">text node</var> to <var title="">new parent</var> as its last
+      <li>Append <var title="">text node</var> to <var title="">new parent</var> as its last
       child.
     </ol>
   </ol>
@@ -796,10 +979,8 @@
 
 <dd><p><strong>Action</strong>: The user agent must run the following steps:
 
-<p class=XXX>We need to delete the selection if it's not collapsed.
-
 <ol>
-  <li><p>If <var title="">value</var> is the empty string, abort these steps and do
+  <li>If <var title="">value</var> is the empty string, abort these steps and do
   nothing.
   <!-- Similar logic to createLink, except even more compelling, since an HTML
   document linking to itself as an image is just silly.  In fact, the current
@@ -807,24 +988,26 @@
   immediately if the URL is empty.  Firefox 4b11 bails out on an empty string,
   but the other three browsers I tested stick in the <img> anyway. -->
 
-  <li><p>Let (<var title="">node</var>, <var title="">offset</var>) be the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>'s
+  <li>Run <a href=http://html5.org/specs/dom-range.html#dom-range-deletecontents><code class=external data-anolis-spec=domrange title=dom-Range-deleteContents>deleteContents()</code></a> on the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>.
+
+  <li>Let (<var title="">node</var>, <var title="">offset</var>) be the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></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>.
 
-  <li><p>Let <var title="">img</var> be a new <a href=#html-element>HTML element</a> with
+  <li>Let <var title="">img</var> be a new <a href=#html-element>HTML element</a> with
   <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> "img", the same <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="">node</var>, and a
   single <a href=#html-attribute>HTML attribute</a> with <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attr-local-name title=concept-attr-local-name>local name</a> "src" and with
   <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attr-value title=concept-attr-value>value</a> <var title="">value</var>.
   <!-- No alt text, so it's invalid.  This matches all browsers. -->
 
-  <li><p>If <var title="">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="">offset</var> is not
+  <li>If <var title="">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="">offset</var> is not
   equal to 0 or 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>, 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="">offset</var>)</code></a> on
   <var title="">node</var>.
 
-  <li><p>If <var title="">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>, <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment><code class=external data-anolis-spec=domcore>Comment</code></a>, or
+  <li>If <var title="">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>, <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment><code class=external data-anolis-spec=domcore>Comment</code></a>, or
   <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction><code class=external data-anolis-spec=domcore>ProcessingInstruction</code></a> node, run <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-insertbefore><code class=external data-anolis-spec=domcore title=dom-Node-insertBefore>insertBefore(<var title="">img</var>,
   <var title="">node</var>)</code></a> on the parent of <var title="">node</var>.
 
-  <li><p>Otherwise, let <var title="">child</var> be the <var title="">offset</var>th child of
+  <li>Otherwise, let <var title="">child</var> be the <var title="">offset</var>th child of
   <var title="">node</var> (or null if there is no such child), and run <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-insertbefore><code class=external data-anolis-spec=domcore title=dom-Node-insertBefore>insertBefore(<var title="">img</var>,
   <var title="">child</var>)</code></a> on <var title="">node</var>.
 </ol>
@@ -866,27 +1049,27 @@
 what the other browsers do instead.
 
 <ol>
-  <li><p>Let <var title="">links</var> be a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-collection title=concept-collection>collection</a> rooted at the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>'s
+  <li>Let <var title="">links</var> be a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-collection title=concept-collection>collection</a> rooted at the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>'s
   <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-root title=concept-range-root>root</a>, whose filter matches only <a href=#html-element title="HTML element">HTML
   elements</a> with <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> "a", that have an <a href=#html-attribute>HTML
   attribute</a> with <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attr-local-name title=concept-attr-local-name>local name</a> "href".
 
-  <li><p>Let <var title="">selected</var> be a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-collection title=concept-collection>collection</a> rooted at the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>'s
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-root title=concept-range-root>root</a>, whose filter matches only <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 that are equal to or after
-  the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>'s <a href=#first-node>first node</a> 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>, and also equal to
-  or before the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>'s <a href=#last-node>last node</a> 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>.
+  <li>Let <var title="">selected</var> be a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-collection title=concept-collection>collection</a> rooted at the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>'s
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-root title=concept-range-root>root</a>, whose filter matches only <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 that are <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in
+  the <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a> and <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> nodes that are <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#partially-contained>partially contained</a> in the
+  <a href=http://html5.org/specs/dom-range.html#range><code class=external data-anolis-spec=domrange>Range</code></a>.
 
-  <li><p>For each <var title="">link</var> in <var title="">links</var>, in <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>:
+  <li>For each <var title="">link</var> in <var title="">links</var>, in <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>:
 
   <ol>
-    <li><p>If <var title="">link</var> is not in <var title="">selected</var> and is not an
+    <li>If <var title="">link</var> is not in <var title="">selected</var> and 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 any <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#node><code class=external data-anolis-spec=domcore>Node</code></a> in <var title="">selected</var>, continue with the next
     <var title="">link</var>.
 
-    <li><p>While <var title="">link</var> has children, insert <var title="">link</var>'s first
+    <li>While <var title="">link</var> has children, insert <var title="">link</var>'s first
     child into its parent as <var title="">link</var>'s previous sibling.
 
-    <li><p>Remove <var title="">link</var>.
+    <li>Remove <var title="">link</var>.
 
     <p class=XXX>This means it disappears even if it had an id, class, etc.
     Maybe not what we want?
--- a/implementation.html	Wed Mar 02 12:17:54 2011 -0700
+++ b/implementation.html	Tue Mar 15 16:00:48 2011 -0600
@@ -14,707 +14,11 @@
 <button tabindex=-1 accesskey=z onclick='execCommand(command.value, null, val.value)'>exec</button>
 <button tabindex=-1 accesskey=x onclick='var ret = queryCommandState(command.value); alert(typeof ret + " \"" + ret + "\"")'>state</button>
 <button tabindex=-1 accesskey=c onclick='var ret = queryCommandValue(command.value); alert(typeof ret + " \"" + ret + "\"")'>value</button>
+<p>Spec convenience links:
+<button tabindex=-1 accesskey=b onclick='myExecCommand("bold", null, "")'><b>B</b></button>
+<button tabindex=-1 accesskey=i onclick='myExecCommand("italic", null, "")'><i>I</i></button>
+<button tabindex=-1 accesskey=u onclick='myExecCommand("underline", null, "")'><u>U</u></button>
 <div contenteditable=true>
 	Abcdef
 </div>
-<script>
-"use strict";
-
-var htmlNamespace = "http://www.w3.org/1999/xhtml";
-
-function indexOf(node) {
-	var ret = 0;
-	while (node != node.parentNode.childNodes[ret]) {
-		ret++;
-	}
-	return ret;
-}
-
-function nodeLength(node) {
-	if (node.nodeType == Node.TEXT_NODE
-	|| node.nodeType == Node.COMMENT_NODE
-	|| node.nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
-		return node.data.length;
-	}
-
-	return node.childNodes.length;
-}
-
-function nextNode(node) {
-	if (node.hasChildNodes()) {
-		return node.firstChild;
-	}
-	return nextNodeDescendants(node);
-}
-
-function previousNode(node) {
-	if (node.previousSibling) {
-		node = node.previousSibling;
-		while (node.hasChildNodes()) {
-			node = node.lastChild;
-		}
-		return node;
-	}
-	if (node.parentNode
-	&& node.parentNode.nodeType == Node.ELEMENT_NODE) {
-		return node.parentNode;
-	}
-	return null;
-}
-
-function nextNodeDescendants(node) {
-	while (node && !node.nextSibling) {
-		node = node.parentNode;
-	}
-	if (!node) {
-		return null;
-	}
-	return node.nextSibling;
-}
-
-function convertProperty(propertyName) {
-	// Special-case for now
-	var map = {
-		"fontStyle": "font-style",
-		"fontWeight": "font-weight",
-		"textDecoration": "text-decoration",
-	};
-	if (typeof map[propertyName] != "undefined") {
-		return map[propertyName];
-	}
-
-	return propertyName;
-}
-
-
-function firstNode(range) {
-	// "If range's start offset is equal to the length of its start node,
-	// return the first Node that is after the start node and all its
-	// descendants (if any) in tree order. If there is no such Node, return the
-	// last Node in the document."
-	if (range.startOffset == nodeLength(range.startContainer)) {
-		var ret = nextNodeDescendants(range.startContainer);
-		if (!ret) {
-			ret = range.startContainer;
-			while (ret.hasChildNodes()) {
-				ret = ret.childNodes[ret.childNodes.length - 1];
-			}
-		}
-		return ret;
-	}
-
-	// "If range's start node is a Text, Comment, or ProcessingInstruction
-	// node, return that."
-	if (range.startContainer.nodeType == Node.TEXT_NODE
-	|| range.startContainer.nodeType == Node.COMMENT_NODE
-	|| range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
-		return range.startContainer;
-	}
-
-	// "If range's start node has children, return the child with index equal
-	// to the start offset."
-	if (range.startContainer.hasChildNodes()) {
-		return range.startContainer.childNodes[range.startOffset];
-	}
-
-	// "Return range's start node."
-	return range.startContainer;
-}
-
-function beginningElement(range) {
-	var first = firstNode(range);
-	if (first.nodeType == Node.ELEMENT_NODE) {
-		return first;
-	}
-
-	if (first.parentNode.nodeType == Node.ELEMENT_NODE) {
-		return first.parentNode;
-	}
-
-	return null;
-}
-
-function activeRange(doc) {
-	// "Let selection be the result of calling getSelection() on the Document."
-	//
-	// We call getSelection() on defaultView instead, because Firefox and Opera
-	// don't follow the DOM Range spec here:
-	// https://bugzilla.mozilla.org/show_bug.cgi?id=636512
-	var selection = doc.defaultView.getSelection();
-
-	// "If there are no Ranges associated with selection, return null."
-	if (selection.rangeCount == 0) {
-		return null;
-	}
-
-	// "Let start be the boundary point with the earliest position among all of
-	// selection's Ranges' starts."
-	var startNode = null;
-	var startOffset = null;
-	for (var i = 0; i < selection.rangeCount; i++) {
-		if (startNode === null) {
-			startNode = selection.getRangeAt(i).startContainer;
-			startOffset = selection.getRangeAt(i).startOffset;
-			continue;
-		}
-		var testRange = doc.createRange();
-		testRange.setStart(startNode, startOffset);
-		if (testRange.compareBoundaryPoints(Range.START_TO_START, selection.getRangeAt(i)) < 0) {
-			startNode = selection.getRangeAt(i).startContainer;
-			startOffset = selection.getRangeAt(i).startOffset;
-		}
-	}
-
-	// "Return the last Range in selection whose start is start."
-	for (var i = selection.rangeCount - 1; i >= 0; i--) {
-		if (selection.getRangeAt(i).startContainer == startNode
-		&& selection.getRangeAt(i).startOffset == startOffset) {
-			return selection.getRangeAt(i);
-		}
-	}
-}
-
-function decomposeRange(range) {
-	// "Let start node, start offset, end node, and end offset be the start and
-	// end nodes and offsets of range, respectively."
-	var startNode = range.startContainer;
-	var startOffset = range.startOffset;
-	var endNode = range.endContainer;
-	var endOffset = range.endOffset;
-
-	// "If start node or end node is not an Element, Text,
-	// ProcessingInstruction, or Comment node, or is not an Element and has no
-	// parent, abort these steps."
-	// Skip the sanity check about node types/detached non-elements
-
-	// "If start node and end node are both Text nodes, and start node is the
-	// same as end node, and neither start offset nor end offset is equal to 0
-	// or the length of start node:"
-	if (startNode.nodeType == Node.TEXT_NODE
-	&& endNode.nodeType == Node.TEXT_NODE
-	&& startNode.isSameNode(endNode)
-	&& startOffset != 0
-	&& startOffset != startNode.data.length
-	&& endOffset != 0
-	&& endOffset != startNode.data.length) {
-		// "Run splitText(start offset) on start node and set start node to the
-		// result."
-		startNode = startNode.splitText(startOffset);
-
-		// "Run splitText(end offset − start offset) on start node and set
-		// start node to the previous sibling of the result."
-		startNode = startNode.splitText(endOffset - startOffset).previousSibling;
-
-		// "Return the list consisting of the single Node start node, and abort
-		// these steps."
-		return [startNode];
-	}
-
-	// "If start node is a Text node and start offset is neither 0 nor the
-	// length of start node, run splitText(start offset) on start node and set
-	// start node to the returned node. Set start offset to 0."
-	if (startNode.nodeType == Node.TEXT_NODE
-	&& startOffset != 0
-	&& startOffset != startNode.data.length) {
-		startNode = startNode.splitText(startOffset);
-		startOffset = 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 and set end node to
-	// the previous sibling of the returned node. Set end offset to the length
-	// of the new end node."
-	if (endNode.nodeType == Node.TEXT_NODE
-	&& endOffset != 0
-	&& endOffset != endNode.data.length) {
-		endNode = endNode.splitText(endOffset).previousSibling;
-		endOffset = endNode.data.length;
-	}
-
-	var node;
-	// "If start node is an Element with at least one child, let node be the
-	// child of start node with index start offset."
-	if (startNode.nodeType == Node.ELEMENT_NODE
-	&& startNode.hasChildNodes()) {
-		node = startNode.childNodes[startOffset];
-	// "Otherwise, if start node is a Text node and start offset is its length,
-	// let node be the first Node after start node in tree order."
-	} else if (startNode.nodeType == Node.TEXT_NODE
-	&& startOffset == startNode.data.length) {
-		node = nextNode(startNode);
-	// "Otherwise, let node be start node."
-	} else {
-		node = startNode;
-	}
-
-	var end;
-	// "If end node is an Element and end offset is not 0, let end be the child
-	// of end node with index end offset − 1."
-	if (endNode.nodeType == Node.ELEMENT_NODE && endOffset != 0) {
-		end = endNode.childNodes[endOffset - 1];
-	// "Otherwise, if end offset is 0, let end be the first Node before end
-	// node in tree order."
-	} else if (endOffset == 0) {
-		end = previousNode(endNode);
-	// "Otherwise, let end be end node."
-	} else {
-		end = endNode;
-	}
-
-	// "While node is the first child of its parent and end is not a descendant
-	// of node's parent, set node to its parent."
-	while (node == node.parentNode.firstChild
-	&& !(end.compareDocumentPosition(node.parentNode) & Node.DOCUMENT_POSITION_CONTAINS)) {
-		node = node.parentNode;
-	}
-
-	// "While end is the last child of its parent and node is not a descendant
-	// of end's parent, set end to its parent."
-	while (end == end.parentNode.lastChild
-	&& !(node.compareDocumentPosition(end.parentNode) & Node.DOCUMENT_POSITION_CONTAINS)) {
-		end = end.parentNode;
-	}
-
-	// "Let node list be an empty list of Nodes."
-	var nodeList = [];
-
-	// "While node is not after end in tree order:"
-	while (!(end.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING)) {
-		// "Append node to node list."
-		nodeList.push(node);
-
-		// "Set node to the first Node in tree order that is after node and (if
-		// applicable) all its descendants. If no such Node exists, break out
-		// of these substeps."
-		node = nextNodeDescendants(node);
-		if (!node) {
-			break;
-		}
-
-		// "While node is an ancestor of end, set node to its first child."
-		while (end.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINS) {
-			node = node.firstChild;
-		}
-	}
-
-	return nodeList;
-}
-
-function unstyleElement(element, propertyName, tagList) {
-	// "Let element children be the Element children of element."
-	var elementChildren = [];
-	for (var j = 0; j < element.childNodes.length; j++) {
-		if (element.childNodes[j].nodeType == Node.ELEMENT_NODE) {
-			elementChildren.push(element.childNodes[j]);
-		}
-	}
-
-	// "Unstyle each Element in element children, in order."
-	for (var j = 0; j < elementChildren.length; j++) {
-		unstyleElement(elementChildren[j], propertyName, tagList);
-	}
-
-	// "If either
-	//
-	// * element is an HTML element with name either "span" or in tag list, and
-	//   it has only a single attribute, and that attribute is named "style",
-	//   and that style attribute sets only the CSS property property name; or
-	//
-	// * element is an HTML element with name in tag list and it has no
-	//   attributes,
-	//
-	// then:"
-	if (
-		(element.namespaceURI == htmlNamespace
-		&& (element.nodeName == "SPAN" || tagList.indexOf(element.nodeName.toLowerCase()) != -1)
-		&& element.attributes.length == 1
-		&& element.attributes[0].localName == "style"
-		&& element.style.length == 1
-		&& element.style.item(0) == convertProperty(propertyName)
-		)
-		||
-		(element.namespaceURI == htmlNamespace
-		&& tagList.indexOf(element.nodeName.toLowerCase()) != -1
-		&& element.attributes.length == 0)
-	) {
-		// "Let children be an empty list of Nodes."
-		var children = [];
-
-		// "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);
-		}
-
-		// "Remove element."
-		element.parentNode.removeChild(element);
-
-		// "Return children."
-		return children;
-	}
-
-	// "Unset the CSS property property name of element."
-	element.style[propertyName] = '';
-	if (element.getAttribute("style") == "") {
-		element.removeAttribute("style");
-	}
-
-	// "If element is not an HTML element or its local name is not in tag list,
-	// return the empty list."
-	if (element.namespaceURI != htmlNamespace
-	|| tagList.indexOf(element.tagName.toLowerCase()) == -1) {
-		return [];
-	}
-
-	// "Let new element be a new HTML element with name "span", with the
-	// same attributes and ownerDocument as element."
-	var newElement = element.ownerDocument.createElement("span");
-	for (var j = 0; j < element.attributes.length; j++) {
-		// FIXME: Namespaces?
-		newElement.setAttribute(element.attributes[j].localName, element.attributes[j].value);
-	}
-
-	// "Append new element to element's parent as the previous sibling of
-	// element."
-	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);
-	}
-
-	// "Remove element."
-	element.parentNode.removeChild(element);
-
-	// "Return the one-Node list consisting of new element."
-	return [newElement];
-}
-
-function styleRange(range, propertyName, propertyValue, tagList) {
-	// Extra step to implement CSS value equality checking; this needs to be
-	// specced somehow.
-	var testEl = document.createElement("span");
-	testEl.style[propertyName] = propertyValue;
-	var expectedPropertyValue = testEl.style[propertyName];
-
-	// "Let node list be the result of decomposing range."
-	var nodeList = decomposeRange(range);
-
-	// "For each node in node list, in tree order:"
-	for (var i = 0; i < nodeList.length; i++) {
-		var node = nodeList[i];
-		// "If node is an Element:"
-		if (node.nodeType == Node.ELEMENT_NODE) {
-			// "Unset the CSS property property name of node."
-			node.style[propertyName] = '';
-			if (node.getAttribute("style") == "") {
-				node.removeAttribute("style");
-			}
-
-			// "If the computed value of property name for node is not property
-			// value, set the CSS property property name of node to property
-			// value."
-			if (getComputedStyle(node)[propertyName] != expectedPropertyValue) {
-				node.style[propertyName] = propertyValue;
-			}
-
-			// "Let element children be the Element children of node."
-			var elementChildren = [];
-			for (var j = 0; j < node.childNodes.length; j++) {
-				if (node.childNodes[j].nodeType == Node.ELEMENT_NODE) {
-					elementChildren.push(node.childNodes[j]);
-				}
-			}
-
-			// "Unstyle each Element in element children, in order."
-			for (var j = 0; j < elementChildren.length; j++) {
-				unstyleElement(elementChildren[j], propertyName, tagList);
-			}
-		// "Otherwise, if node is a Text node:"
-		} else if (node.nodeType == Node.TEXT_NODE) {
-			// "Let new parent be a new HTML element with local name equal to
-			// the first string in tag list (or equal to "span" if tag list is
-			// empty), with no attributes, and ownerDocument the same as node."
-			var newParent = node.ownerDocument.createElement(tagList.length ? tagList[0] : "span");
-
-			// "Append new parent to node's parent as the previous sibling of
-			// node."
-			node.parentNode.insertBefore(newParent, node);
-
-			// "If the computed value of property name on new parent is not
-			// equal to property value, set the CSS property property name of
-			// new parent to property value."
-			if (getComputedStyle(newParent)[propertyName] != expectedPropertyValue) {
-				newParent.style[propertyName] = propertyValue;
-			}
-
-			// "Append node to new parent as its last child."
-			newParent.appendChild(node);
-		}
-		// "Otherwise, do nothing."
-	}
-}
-
-function unstyleRange(range, propertyName, propertyValue, tagList) {
-	// Extra step to implement CSS value equality checking; this needs to be
-	// specced somehow.
-	var testEl = document.createElement("span");
-	testEl.style[propertyName] = propertyValue;
-	var expectedPropertyValue = testEl.style[propertyName];
-
-	// "Let node list be the result of decomposing range."
-	var nodeList = decomposeRange(range);
-
-	// "For each node in node list, in order:"
-	for (var i = 0; i < nodeList.length; i++) {
-		var node = nodeList[i];
-
-		// "If node is an Element:"
-		if (node.nodeType == Node.ELEMENT_NODE) {
-			// "Let extra nodes be the result of unstyling node."
-			var extraNodes = unstyleElement(node, propertyName, tagList);
-
-			// "If node no longer has a parent:"
-			if (!node.parentNode) {
-				// "Insert all the Nodes in extra nodes into node list
-				// immediately after node, in order."
-				//
-				// splice() would be perfect, but it requires varargs.  :(
-				nodeList = nodeList.slice(0, i + 1)
-					.concat(extraNodes)
-					.concat(nodeList.slice(i + 1));
-
-				// "Continue with the next Node in node list, if any."
-				continue;
-			}
-
-			// "If the computed value of property name for node is not property
-			// value, set the CSS property property name of node to property
-			// value."
-			if (getComputedStyle(node)[propertyName] != expectedPropertyValue) {
-				node.style[propertyName] = propertyValue;
-			}
-
-			// "Let element children be the Element children of node."
-			var elementChildren = [];
-			for (var j = 0; j < node.childNodes.length; j++) {
-				if (node.childNodes[j].nodeType == Node.ELEMENT_NODE) {
-					elementChildren.push(node.childNodes[j]);
-				}
-			}
-
-			// "Unstyle each Element in element children, in order."
-			for (var j = 0; j < elementChildren.length; j++) {
-				unstyleElement(elementChildren[j], propertyName, tagList);
-			}
-		// "Otherwise, if node is a Text node and the computed value of
-		// property name for node's parent is not property value:"
-		} else if (node.nodeType == Node.TEXT_NODE
-		&& getComputedStyle(node.parentNode)[propertyName] != expectedPropertyValue) {
-			// "Let new parent be a new HTML element with name "span", with no
-			// attributes, and with ownerDocument equal to node's."
-			var newParent = node.ownerDocument.createElement("span");
-
-			// "Set the CSS property property name of new parent to property
-			// value."
-			newParent.style[propertyName] = propertyValue;
-
-			// "Insert new parent as node's previous sibling."
-			node.parentNode.insertBefore(newParent, node);
-
-			// "Append node to new parent as its child."
-			newParent.appendChild(node);
-		}
-		// "Otherwise, do nothing."
-	}
-}
-
-function myExecCommand(commandId, showUI, value) {
-	commandId = commandId.toLowerCase();
-	var range = activeRange(document);
-
-	if (!range) {
-		return;
-	}
-
-	switch (commandId) {
-		case "bold":
-		if (getState("bold", range)) {
-			unstyleRange(range, "fontWeight", "normal", ["b", "strong"]);
-		} else {
-			styleRange(range, "fontWeight", "bold", ["b", "strong"]);
-		}
-		break;
-
-		case "createlink":
-		// "If value is the empty string, do nothing."
-		if (value === "") {
-			break;
-		}
-
-		// "Let node list be the result of decomposing the Range."
-		var nodeList = decomposeRange(range);
-
-		// "For each node in node list, in order:"
-		for (var i = 0; i < nodeList.length; i++) {
-			var node = nodeList[i];
-
-			// "Let text nodes be a list of all Text node descendants of node,
-			// or node itself if it's a Text node."
-			var textNodes = [];
-			if (node.nodeType == Node.TEXT_NODE) {
-				textNodes.push(node);
-			} else {
-				for (var cur = node.firstChild;
-				cur && cur.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINS;
-				cur = nextNode(cur)) {
-					if (cur.nodeType == Node.TEXT_NODE) {
-						textNodes.push(cur);
-					}
-				}
-			}
-
-			// "For each text node in text nodes, in tree order:"
-			for (var j = 0; j < textNodes.length; j++) {
-				var textNode = textNodes[j];
-
-				// "Let ancestor link be the parent of text node."
-				var ancestorLink = textNode.parentNode;
-
-				// "While ancestor link is not an HTML element, or its local
-				// name is not "a", or it has no HTML attribute with local name
-				// "href":"
-				while (ancestorLink.namespaceURI != htmlNamespace
-				|| ancestorLink.nodeType != Node.ELEMENT_NODE
-				|| ancestorLink.tagName != "A"
-				|| !ancestorLink.hasAttribute("href")) {
-					// "If the parent of ancestor link is not an Element, set
-					// ancestor link to null and break from this loop."
-					if (!ancestorLink.parentNode
-					|| ancestorLink.parentNode.nodeType != Node.ELEMENT_NODE) {
-						ancestorLink = null;
-						break;
-					}
-
-					// "Otherwise, set ancestor link to its parent."
-					ancestorLink = ancestorLink.parentNode;
-				}
-
-				// "If ancestor link is not null, set its "href" attribute to
-				// value and continue with the next text node."
-				if (ancestorLink) {
-					ancestorLink.setAttribute("href", value);
-					continue;
-				}
-
-				// "Let new parent be a new HTML element with local name "a",
-				// the same ownerDocument as text node, and a single HTML
-				// attribute with local name "href" and value value."
-				var newParent = textNode.ownerDocument.createElement("a");
-				newParent.setAttribute("href", value);
-
-				// "Insert new parent into text node's parent as the previous
-				// sibling of text node."
-				textNode.parentNode.insertBefore(newParent, textNode);
-
-				// "Append text node to new parent as its last child."
-				newParent.appendChild(textNode);
-			}
-		}
-
-		case "foreColor":
-		// Hacky test to see if the color is valid
-		var testEl = document.createElement("span");
-		testEl.style.color = value;
-		if (testEl.style.color === "") {
-			return;
-		}
-		styleRange(range, "color", value, []);
-		break;
-
-		case "italic":
-		if (getState("italic", range)) {
-			unstyleRange(range, "fontStyle", "normal", ["i", "em"]);
-		} else {
-			styleRange(range, "fontStyle", "italic", ["i", "em"]);
-		}
-		break;
-
-		default:
-		break;
-	}
-}
-
-function myQueryCommandState(commandId) {
-	commandId = commandId.toLowerCase();
-	var range = activeRange(document);
-
-	if (!range) {
-		return false;
-	}
-
-	return getState(commandId, range);
-}
-
-function getState(commandId, range) {
-	var style = getComputedStyle(beginningElement(range));
-
-	switch (commandId) {
-		case "bold":
-		return style.fontWeight == "bold"
-			|| (/^[0-9]+$/.test(style.fontWeight) && style.fontWeight >= 700);
-
-		case "italic":
-		return style.fontStyle == "italic" || style.fontStyle == "oblique";
-
-		default:
-		return false;
-	}
-}
-
-function myQueryCommandValue(commandId) {
-	commandId = commandId.toLowerCase();
-	var range = activeRange(document);
-
-	if (!range) {
-		return "";
-	}
-
-	var style = getComputedStyle(beginningElement(range));
-
-	switch (commandId) {
-		case "backcolor":
-		// "Let element be the beginning element of the Range."
-		var element = beginningElement(range);
-		// "While the computed style of "background-color" on element is any
-		// fully transparent value, set element to its parent."
-		while (element.nodeType == Node.ELEMENT_NODE
-		&& (getComputedStyle(element).backgroundColor == "rgba(0, 0, 0, 0)"
-		|| getComputedStyle(element).backgroundColor === ""
-		|| getComputedStyle(element).backgroundColor == "transparent")) {
-			element = element.parentNode;
-		}
-		// "Return the computed style of "background-color" for element."
-		if (element.nodeType != Node.ELEMENT_NODE) {
-			return 'rgb(255, 255, 255)';
-		}
-		return getComputedStyle(element).backgroundColor;
-
-		case "fontname":
-		return style.fontFamily;
-
-		case "forecolor":
-		return style.color;
-
-		default:
-		return "";
-	}
-}
-</script>
+<script src=implementation.js></script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/implementation.js	Tue Mar 15 16:00:48 2011 -0600
@@ -0,0 +1,1085 @@
+"use strict";
+
+var htmlNamespace = "http://www.w3.org/1999/xhtml";
+
+function getNodeIndex(node) {
+	var ret = 0;
+	while (node != node.parentNode.childNodes[ret]) {
+		ret++;
+	}
+	return ret;
+}
+
+function getNodeLength(node) {
+	if (node.nodeType == Node.TEXT_NODE
+	|| node.nodeType == Node.COMMENT_NODE
+	|| node.nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
+		return node.data.length;
+	}
+
+	return node.childNodes.length;
+}
+
+function nextNode(node) {
+	if (node.hasChildNodes()) {
+		return node.firstChild;
+	}
+	return nextNodeDescendants(node);
+}
+
+function previousNode(node) {
+	if (node.previousSibling) {
+		node = node.previousSibling;
+		while (node.hasChildNodes()) {
+			node = node.lastChild;
+		}
+		return node;
+	}
+	if (node.parentNode
+	&& node.parentNode.nodeType == Node.ELEMENT_NODE) {
+		return node.parentNode;
+	}
+	return null;
+}
+
+function nextNodeDescendants(node) {
+	while (node && !node.nextSibling) {
+		node = node.parentNode;
+	}
+	if (!node) {
+		return null;
+	}
+	return node.nextSibling;
+}
+
+function convertProperty(propertyName) {
+	// Special-case for now
+	var map = {
+		"fontStyle": "font-style",
+		"fontWeight": "font-weight",
+		"textDecoration": "text-decoration",
+	};
+	if (typeof map[propertyName] != "undefined") {
+		return map[propertyName];
+	}
+
+	return propertyName;
+}
+
+function cssValuesEqual(propertyName, val1, val2) {
+	// This is a bad hack to work around browser incompatibility.  It wouldn't
+	// work in real life, but it's good enough for a test implementation.
+	var test1 = document.createElement("span");
+	test1.style[propertyName] = val1;
+	var test2 = document.createElement("span");
+	test2.style[propertyName] = val2;
+
+	return test1.style[propertyName] == test2.style[propertyName];
+}
+
+
+/**
+ * The position of two boundary points relative to one another, as defined by
+ * the spec.
+ */
+function getPosition(nodeA, offsetA, nodeB, offsetB) {
+	// "If node A is the same as node B, return equal if offset A equals offset
+	// B, before if offset A is less than offset B, and after if offset A is
+	// greater than offset B."
+	if (nodeA == nodeB) {
+		if (offsetA == offsetB) {
+			return "equal";
+		}
+		if (offsetA < offsetB) {
+			return "before";
+		}
+		if (offsetA > offsetB) {
+			return "after";
+		}
+	}
+
+	// "If node A is after node B in tree order, compute the position of (node
+	// B, offset B) relative to (node A, offset A). If it is before, return
+	// after. If it is after, return before."
+	if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_FOLLOWING) {
+		var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
+		if (pos == "before") {
+			return "after";
+		}
+		if (pos == "after") {
+			return "before";
+		}
+	}
+
+	// "If node A is an ancestor of node B:"
+	if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_CONTAINS) {
+		// "Let child equal node B."
+		var child = nodeB;
+
+		// "While child is not a child of node A, set child to its parent."
+		while (child.parentNode != nodeA) {
+			child = child.parentNode;
+		}
+
+		// "If the index of child is less than offset A, return after."
+		if (getNodeIndex(child) < offsetA) {
+			return "after";
+		}
+	}
+
+	// "Return before."
+	return "before";
+}
+
+/**
+ * Returns the furthest ancestor of a Node as defined by DOM Range.
+ */
+function getFurthestAncestor(node) {
+	var root = node;
+	while (root.parentNode != null) {
+		root = root.parentNode;
+	}
+	return root;
+}
+
+/**
+ * "contained" as defined by DOM Range: "A Node node is contained in a range
+ * range if node's furthest ancestor is the same as range's root, and (node, 0)
+ * is after range's start, and (node, length of node) is before range's end."
+ */
+function isContained(node, range) {
+	var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
+	var pos2 = getPosition(node, getNodeLength(node), range.endContainer, range.endOffset);
+
+	return getFurthestAncestor(node) == getFurthestAncestor(range.startContainer)
+		&& pos1 == "after"
+		&& pos2 == "before";
+}
+
+function isHtmlElement(node) {
+	return node
+		&& node.nodeType == Node.ELEMENT_NODE
+		&& node.namespaceURI == htmlNamespace;
+}
+
+function beginningElement(range) {
+	// "If the start node of the Range is a Text, Comment, or
+	// ProcessingInstruction node, and the start offset of the Range is not
+	// equal to the length of its start node, let first node be the Range's
+	// start node."
+	var firstNode = null;
+	if (range.startOffset != getNodeLength(range.startContainer)
+	&& (range.startContainer.nodeType == Node.TEXT_NODE
+	|| range.startContainer.nodeType == Node.COMMENT_NODE
+	|| range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE)) {
+		firstNode = range.startContainer;
+	// "Otherwise, let first node be the first Node in tree order that is
+	// contained in the Range, if there is any."
+	} else {
+		var firstContained = range.startContainer;
+		while (firstContained != range.endContainer
+		&& !isContained(firstContained, range)) {
+			firstContained = nextNode(firstContained);
+		}
+		if (firstContained != range.endContainer) {
+			firstNode = firstContained;
+		}
+	}
+
+	// "If first node is defined and is an Element, return first node."
+	if (firstNode && firstNode.nodeType == Node.ELEMENT_NODE) {
+		return firstNode;
+	}
+
+	// "Otherwise, if first node is defined and its parent is an Element,
+	// return first node's parent."
+	if (firstNode
+	&& firstNode.parentNode
+	&& firstNode.parentNode.nodeType == Node.ELEMENT_NODE) {
+		return firstNode.parentNode;
+	}
+
+	// "Return null."
+	return null;
+}
+
+function activeRange(doc) {
+	// "Let selection be the result of calling getSelection() on the Document."
+	//
+	// We call getSelection() on defaultView instead, because Firefox and Opera
+	// don't follow the DOM Range spec here:
+	// https://bugzilla.mozilla.org/show_bug.cgi?id=636512
+	var selection = doc.defaultView.getSelection();
+
+	// "If there are no Ranges associated with selection, return null."
+	if (selection.rangeCount == 0) {
+		return null;
+	}
+
+	// "Let start be the boundary point with the earliest position among all of
+	// selection's Ranges' starts."
+	var startNode = null;
+	var startOffset = null;
+	for (var i = 0; i < selection.rangeCount; i++) {
+		if (startNode === null) {
+			startNode = selection.getRangeAt(i).startContainer;
+			startOffset = selection.getRangeAt(i).startOffset;
+			continue;
+		}
+		var testRange = doc.createRange();
+		testRange.setStart(startNode, startOffset);
+		if (testRange.compareBoundaryPoints(Range.START_TO_START, selection.getRangeAt(i)) < 0) {
+			startNode = selection.getRangeAt(i).startContainer;
+			startOffset = selection.getRangeAt(i).startOffset;
+		}
+	}
+
+	// "Return the last Range in selection whose start is start."
+	for (var i = selection.rangeCount - 1; i >= 0; i--) {
+		if (selection.getRangeAt(i).startContainer == startNode
+		&& selection.getRangeAt(i).startOffset == startOffset) {
+			return selection.getRangeAt(i);
+		}
+	}
+}
+
+/**
+ * "Given a CSS property name property name, an (optional) value property value
+ * for that property, and a possibly empty list of strings tag list, a Node is
+ * a potentially relevant styling element if it is an HTML element and one of
+ * the following holds:
+ *
+ *  * Its local name is in tag list and it has no attributes.
+ *  * Its local name is in tag list or is "span" or is "font", and it has
+ *    exactly one attribute, and that attribute is an HTML attribute with local
+ *    name "style", and that attribute sets exactly one CSS property, and that
+ *    property is property name, and either property value is undefined or the
+ *    value the attribute sets the property to is property value.
+ *  * Its local name is "font", and it has exactly one attribute, and that
+ *    attribute is a color attribute, and either property value is undefined or
+ *    the effect of the attribute is to hint that the CSS color attribute be
+ *    set to property value, and property name is "color".
+ *  * Its local name is "font", and it has exactly one attribute, and that
+ *    attribute is a face attribute, and either property value is undefined or
+ *    the effect of the attribute is to hint that the CSS font-family attribute
+ *    be set to property value, and property name is "font-family".
+ *  * Its local name is "font", and it has exactly one attribute, and that
+ *    attribute is a size attribute, and either property value is undefined or
+ *    the effect of the attribute is to hint that the CSS font-size attribute
+ *    be set to property value, and property name is "font-size"."
+ */
+function isPotentiallyRelevantStylingElement(element, propertyName, propertyValue, tagList) {
+	if (!isHtmlElement(element)) {
+		return false;
+	}
+
+	var localName = element.tagName.toLowerCase();
+
+	if (tagList.indexOf(localName) != -1 && element.attributes.length == 0) {
+		return true;
+	}
+
+	if ((tagList.indexOf(localName) != -1 || localName == "span" || localName == "font")
+	&& element.attributes.length == 1
+	// Not checking namespace because it seems buggy, maybe?
+	//&& element.attributes[0].namespaceURI == htmlNamespace
+	&& element.attributes[0].localName == "style"
+	&& element.style.length == 1
+	&& element.style.item(0) == convertProperty(propertyName)
+	&& (propertyValue === null
+	|| cssValuesEqual(propertyName, element.style[propertyName], propertyValue))) {
+		return true;
+	}
+
+	var fontAttr = null;
+	if (propertyName == "color") {
+		fontAttr = "color";
+	} else if (propertyName == "fontFamily") {
+		fontAttr = "face";
+	} else if (propertyName == "fontSize") {
+		fontAttr = "size";
+	}
+
+	// TODO: cssValuesEqual() is total nonsense for font-size.
+	if (fontAttr
+	&& localName == "font"
+	&& element.attributes.length == 1
+	//&& element.attributes[0].namespaceURI == htmlNamespace
+	&& element.attributes[0].localName == fontAttr
+	&& (propertyValue === null
+	|| cssValuesEqual(propertyName, element[fontAttr], propertyValue))) {
+		return true;
+	}
+}
+
+/**
+ * "A Node is a relevant styling element if it is a potentially relevant
+ * styling element, and its CSS property property name computes to property
+ * value."
+ */
+function isRelevantStylingElement(node, propertyName, propertyValue, tagList) {
+	return isPotentiallyRelevantStylingElement(node, propertyName, propertyValue, tagList)
+		&& cssValuesEqual(propertyName, getComputedStyle(node)[propertyName], propertyValue);
+}
+
+/**
+ * "A phrasing element is either an HTML element that is categorized as
+ * phrasing content, or a non-conforming HTML element (which thus has no
+ * categories), or an Element that is not an HTML element."
+ */
+function isPhrasingElement(element) {
+	if (!element || element.nodeType != Node.ELEMENT_NODE) {
+		return false;
+	}
+
+	if (!isHtmlElement(element)) {
+		return true;
+	}
+
+	// As of March 2011.
+	var nonConforming = ["applet", "acronym", "bgsound", "dir", "frame",
+	"frameset", "noframes", "isindex", "listing", "xmp", "nextid", "noembed",
+	"plaintext", "rb", "strike", "basefont", "big", "blink", "center", "font",
+	"marquee", "multicol", "nobr", "spacer", "tt", "u"];
+
+	// I'm skipping checks for elements that are only sometimes phrasing
+	// content.  I just assume they always are.
+	var phrasingElements = ["a", "abbr", "area", "audio", "b", "bdi", "bdo",
+	"br", "button", "canvas", "cite", "code", "command", "datalist", "del",
+	"dfn", "em", "embed", "i", "iframe", "img", "input", "ins", "kbd",
+	"keygen", "label", "link", "map", "mark", "math", "meta", "meter",
+	"noscript", "object", "output", "progress", "q", "ruby", "s", "samp",
+	"script", "select", "small", "span", "strong", "sub", "sup", "svg",
+	"textarea", "time", "var", "video", "wbr"];
+
+	return nonConforming.indexOf(element.tagName.toLowerCase()) != -1
+		|| phrasingElements.indexOf(element.tagName.toLowerCase()) != -1;
+}
+
+/**
+ * "specified style" per spec
+ */
+function getSpecifiedStyle(element, propertyName) {
+	// "If the Element has a style attribute set, and that attribute has the
+	// effect of setting property name, return the value that it sets property
+	// name to."
+	if (element.style[propertyName] != "") {
+		return element.style[propertyName];
+	}
+
+	// "If the Element is a font element that has an attribute whose effect is
+	// to create a presentational hint for property name, return the value that
+	// the hint sets property name to."
+	//
+	// I'm cheating on this one for simplicity.  Font-size is especially wrong.
+	if (element.namespaceURI == htmlNamespace
+	&& element.tagName == "FONT") {
+		if (propertyName == "color" && element.hasAttribute("color")) {
+			return element.color;
+		}
+		if (propertyName == "fontFamily" && element.hasAttribute("face")) {
+			return element.face;
+		}
+		if (propertyName == "fontSize" && element.hasAttribute("size")) {
+			return element.size;
+		}
+	}
+
+	// "If the Element is in the following list, and property name is equal to
+	// the CSS property name listed for it, return the string listed for it."
+	//
+	// A list follows, whose meaning is copied here.
+	if (propertyName == "fontWeight"
+	&& (element.tagName == "B" || element.tagName == "STRONG")) {
+		return "bold";
+	}
+	if (propertyName == "fontStyle"
+	&& (element.tagName == "I" || element.tagName == "EM")) {
+		return "italic";
+	}
+	if (propertyName == "textDecoration"
+	&& element.tagName == "U") {
+		return "underline";
+	}
+
+	// "Return null."
+	return null;
+}
+
+function decomposeRange(range) {
+	// "If range's start and end are the same, return an empty list."
+	if (range.startContainer == range.endContainer
+	&& range.startOffset == range.endOffset) {
+		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 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 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);
+
+	// "Return a list consisting of every Node contained in range in tree
+	// order, omitting any whose parent is also contained in range."
+	var ret = [];
+	for (var node = startNode; node != nextNodeDescendants(endNode); node = nextNode(node)) {
+		if (isContained(node, range)
+		&& !isContained(node.parentNode, range)) {
+			ret.push(node);
+		}
+	}
+	return ret;
+}
+
+function clearStyles(element, propertyName, tagList) {
+	// "If element is a potentially relevant styling element:"
+	if (isPotentiallyRelevantStylingElement(element, propertyName, null, tagList)) {
+		// "Let children be an empty list of Nodes."
+		var children = [];
+
+		// "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);
+		}
+
+		// "Remove element."
+		element.parentNode.removeChild(element);
+
+		// "Return children."
+		return children;
+	}
+
+	// "Unset the CSS property property name of element."
+	element.style[propertyName] = '';
+	if (element.getAttribute("style") == "") {
+		element.removeAttribute("style");
+	}
+
+	// "If element is a font element:"
+	if (element.namespaceURI == htmlNamespace && element.tagName == "FONT") {
+		// "If property name is "color", unset element's color attribute, if
+		// set."
+		if (propertyName == "color") {
+			element.removeAttribute("color");
+		}
+
+		// "If property name is "font-family", unset element's face attribute,
+		// if set."
+		if (propertyName == "fontFamily") {
+			element.removeAttribute("face");
+		}
+
+		// "If property name is "font-size", unset element's size attribute, if
+		// set."
+		if (propertyName == "fontSize") {
+			element.removeAttribute("size");
+		}
+	}
+
+	// "If element is not an HTML element or its local name is not in tag list,
+	// return the empty list."
+	if (element.namespaceURI != htmlNamespace
+	|| tagList.indexOf(element.tagName.toLowerCase()) == -1) {
+		return [];
+	}
+
+	// "Let new element be a new HTML element with name "span", with the
+	// same attributes and ownerDocument as element."
+	var newElement = element.ownerDocument.createElement("span");
+	for (var j = 0; j < element.attributes.length; j++) {
+		// FIXME: Namespaces?
+		newElement.setAttribute(element.attributes[j].localName, element.attributes[j].value);
+	}
+
+	// "Append new element to element's parent as the previous sibling of
+	// element."
+	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);
+	}
+
+	// "Remove element."
+	element.parentNode.removeChild(element);
+
+	// "Return the one-Node list consisting of new element."
+	return [newElement];
+}
+
+function recursivelyClearStyles(element, propertyName, tagList) {
+	// "Let element children be the Element children of element."
+	var elementChildren = [];
+	for (var j = 0; j < element.childNodes.length; j++) {
+		if (element.childNodes[j].nodeType == Node.ELEMENT_NODE) {
+			elementChildren.push(element.childNodes[j]);
+		}
+	}
+
+	// "Recursively clear styles on each Element in element children."
+	for (var j = 0; j < elementChildren.length; j++) {
+		recursivelyClearStyles(elementChildren[j], propertyName, tagList);
+	}
+
+	// "Clear styles on element, and return the resulting list."
+	return clearStyles(element, propertyName, tagList);
+}
+
+function styleNode(node, propertyName, propertyValue, tagList) {
+	// "If node's parent is null, or if node is not an Element, Text, Comment,
+	// or ProcessingInstruction node, abort this algorithm."
+	if (!node.parentNode
+	|| [Node.ELEMENT_NODE, Node.TEXT_NODE, Node.COMMENT_NODE,
+	Node.PROCESSING_INSTRUCTION_NODE].indexOf(node.nodeType) == -1) {
+		return;
+	}
+
+	// "If node is an Element:"
+	if (node.nodeType == Node.ELEMENT_NODE) {
+		// "Clear styles on node, and let new nodes be the result."
+		var newNodes = clearStyles(node, propertyName, tagList);
+
+		// "For each new node in new nodes, style new node, with the same
+		// inputs as this invocation of the algorithm."
+		for (var i = 0; i < newNodes.length; i++) {
+			styleNode(newNodes[i], propertyName, propertyValue, tagList);
+		}
+
+		// "If node's parent is null, abort this algorithm."
+		if (!node.parentNode) {
+			return;
+		}
+	}
+
+	// "If node is an Element but not a phrasing element:"
+	if (node.nodeType == Node.ELEMENT_NODE
+	&& !isPhrasingElement(node)) {
+		// "Let children be all children of node, omitting any that are
+		// Elements whose specified style for property name is neither null nor
+		// equal to property value."
+		var children = [];
+		for (var i = 0; i < node.childNodes.length; i++) {
+			if (node.childNodes[i].nodeType == Node.ELEMENT_NODE) {
+				var specifiedStyle = getSpecifiedStyle(node.childNodes[i], propertyName);
+
+				if (specifiedStyle !== null
+				&& !cssValuesEqual(propertyName, propertyValue, specifiedStyle)) {
+					continue;
+				}
+			}
+			children.push(node.childNodes[i]);
+		}
+
+		// "Style each Node in children."
+		for (var i = 0; i < children.length; i++) {
+			styleNode(children[i], propertyName, propertyValue, tagList);
+		}
+
+		// "Abort this algorithm."
+		return;
+	}
+
+	// "If node's previousSibling is a relevant styling element, append node as
+	// the last child of its previousSibling and abort this algorithm."
+	if (isRelevantStylingElement(node.previousSibling, propertyName, propertyValue, tagList)) {
+		node.previousSibling.appendChild(node);
+		return;
+	}
+
+	// "If node's nextSibling is a relevant styling element, insert node as the
+	// first child of its nextSibling and abort this algorithm."
+	if (isRelevantStylingElement(node.nextSibling, propertyName, propertyValue, tagList)) {
+		node.nextSibling.insertBefore(node, node.nextSibling.childNodes.length
+			? node.nextSibling.childNodes[0]
+			: null);
+		return;
+	}
+
+	// "If node is a Comment or ProcessingInstruction, abort this algorithm."
+	if (node.nodeType == Node.COMMENT_NODE
+	|| node.nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
+		return;
+	}
+
+	// "If node is an Element and the computed style of property name for it is
+	// property value, abort this algorithm."
+	if (node.nodeType == Node.ELEMENT_NODE
+	&& cssValuesEqual(propertyName, getComputedStyle(node)[propertyName], propertyValue)) {
+		return;
+	}
+
+	// "If node is a Text node and the computed style of property name for its
+	// parent is property value, abort this algorithm."
+	if (node.nodeType == Node.TEXT_NODE
+	&& cssValuesEqual(propertyName, getComputedStyle(node.parentNode)[propertyName], propertyValue)) {
+		return;
+	}
+
+	// "Let tag be the first string in tag list, if that is not empty, or
+	// "span" if it is empty."
+	var tag = tagList.length ? tagList[0] : "span";
+
+	// "Let new parent be the result of calling createElement(tag) on the
+	// ownerDocument of node."
+	var newParent = node.ownerDocument.createElement(tag);
+
+	// "Insert new parent in node's parent before node."
+	node.parentNode.insertBefore(newParent, node);
+
+	// "If the computed value of property name for new parent is not property
+	// value, set the CSS property property name of new parent to property
+	// value."
+	if (!cssValuesEqual(propertyName, getComputedStyle(newParent)[propertyName], propertyValue)) {
+		newParent.style[propertyName] = propertyValue;
+	}
+
+	// "Append node to new parent as its last child."
+	newParent.appendChild(node);
+}
+
+function recursivelyStyleNode(node, propertyName, propertyValue, tagList) {
+	// "If node's parent is null, or if node is not an Element, Text, Comment,
+	// or ProcessingInstruction node, abort this algorithm."
+	if (!node.parentNode
+	|| [Node.ELEMENT_NODE, Node.TEXT_NODE, Node.COMMENT_NODE,
+	Node.PROCESSING_INSTRUCTION_NODE].indexOf(node.nodeType) == -1) {
+		return;
+	}
+
+	// "If node is an Element:"
+	if (node.nodeType == Node.ELEMENT_NODE) {
+		// "Recursively clear styles on node, and let new nodes be the result."
+		var newNodes = recursivelyClearStyles(node, propertyName, tagList);
+
+		// "For each new node in new nodes, recursively style new node, with
+		// the same inputs as this invocation of the algorithm."
+		for (var i = 0; i < newNodes.length; i++) {
+			recursivelyStyleNode(newNodes[i], propertyName, propertyValue, tagList);
+		}
+
+		// "If node's parent is null, abort this algorithm."
+		if (!node.parentNode) {
+			return;
+		}
+	}
+
+	// "Style node."
+	styleNode(node, propertyName, propertyValue, tagList);
+}
+
+// "When a user agent is to style a Range, it must decompose the Range, then
+// recursively style each Node in the returned list."
+function styleRange(range, propertyName, propertyValue, tagList) {
+	var nodeList = decomposeRange(range);
+	for (var i = 0; i < nodeList.length; i++) {
+		recursivelyStyleNode(nodeList[i], propertyName, propertyValue, tagList);
+	}
+}
+
+function unstyleNode(node, propertyName, newValue, tagList) {
+	// "If node's parent is null, or if node is not an Element or Text node,
+	// abort this algorithm."
+	if (!node.parentNode
+	|| (node.nodeType != Node.ELEMENT_NODE && node.nodeType != Node.TEXT_NODE)) {
+		return;
+	}
+
+	// "If node is an Element:"
+	if (node.nodeType == Node.ELEMENT_NODE) {
+		// "Recursively clear styles on node, and let new nodes be the result."
+		var newNodes = recursivelyClearStyles(node, propertyName, tagList);
+
+		// "For each new node in new nodes, unstyle new node, with the same
+		// inputs as this invocation of the algorithm."
+		for (var i = 0; i < newNodes.length; i++) {
+			unstyleNode(newNodes[i], propertyName, newValue, tagList);
+		}
+
+		// "If node's parent is null, abort this algorithm."
+		if (!node.parentNode) {
+			return;
+		}
+	}
+
+	// "If node is an Element, let current value equal the computed value of
+	// property name on node. Otherwise, let current value equal the computed
+	// value of property name on node's parent."
+	var currentValue;
+	if (node.nodeType == Node.ELEMENT_NODE) {
+		currentValue = getComputedStyle(node)[propertyName];
+	} else {
+		currentValue = getComputedStyle(node.parentNode)[propertyName];
+	}
+
+	// "If current value equals new value, abort this algorithm."
+	if (cssValuesEqual(propertyName, currentValue, newValue)) {
+		return;
+	}
+
+	// "Let ancestor list be a list of Nodes, initially empty."
+	var ancestorList = [];
+
+	// "Let current ancestor equal node."
+	var currentAncestor = node;
+
+	// "While current ancestor's parent is an Element, set current ancestor to
+	// its parent, then append it to ancestor list."
+	while (currentAncestor.parentNode
+	&& currentAncestor.parentNode.nodeType == Node.ELEMENT_NODE) {
+		currentAncestor = currentAncestor.parentNode;
+		ancestorList.push(currentAncestor);
+	}
+
+	// "While ancestor list is not empty, and the last member of ancestor list
+	// has specified style for property name equal to new value or null, remove
+	// the last member from ancestor list."
+	while (ancestorList.length
+	&& (getSpecifiedStyle(ancestorList[ancestorList.length - 1], propertyName) === null
+	|| cssValuesEqual(propertyName, newValue, getSpecifiedStyle(ancestorList[ancestorList.length - 1], propertyName)))) {
+		ancestorList.pop();
+	}
+
+	// "While ancestor list is not empty:"
+	while (ancestorList.length) {
+		// "Let current ancestor be the last member of ancestor list."
+		// "Remove the last member from ancestor list."
+		var currentAncestor = ancestorList.pop();
+
+		// "Let propagated value be the specified style of current ancestor for
+		// property name."
+		var propagatedValue = getSpecifiedStyle(currentAncestor, propertyName);
+
+		// "If propagated value is null, continue this loop from the
+		// beginning."
+		if (propagatedValue === null) {
+			continue;
+		}
+
+		// "Let children be the children of current ancestor."
+		var children = [];
+		for (var i = 0; i < currentAncestor.childNodes.length; i++) {
+			children.push(currentAncestor.childNodes[i]);
+		}
+
+		// "Clear styles on current ancestor."
+		clearStyles(currentAncestor, propertyName, tagList);
+
+		// "For every child in children:"
+		for (var i = 0; i < children.length; i++) {
+			var child = children[i];
+
+			// "If child is node, continue with the next child."
+			if (child == node) {
+				continue;
+			}
+
+			// "If child is an Element whose specified style for property name
+			// is neither null nor equal to propagated value, continue with the
+			// next child."
+			if (child.nodeType == Node.ELEMENT_NODE
+			&& getSpecifiedStyle(child, propertyName) !== null
+			&& !cssValuesEqual(propertyName, propagatedValue, getSpecifiedStyle(child, propertyName))) {
+				continue;
+			}
+
+			// "If child is the last member of ancestor list, set child's CSS
+			// property property name to propagated value and continue with the
+			// next child."
+			if (child == ancestorList[ancestorList.length - 1]) {
+				child.style[propertyName] = propagatedValue;
+				continue;
+			}
+
+			// "Style child, with property name and tag list as in this
+			// algorithm, and property value equal to propagated value."
+			styleNode(child, propertyName, propagatedValue, tagList);
+		}
+	}
+
+	// "If node is an Element and property name does not compute to new value
+	// on it, set property name to new value on it."
+	if (node.nodeType == Node.ELEMENT_NODE
+	&& !cssValuesEqual(propertyName, newValue, getComputedStyle(node)[propertyName])) {
+		node.style[propertyName] = newValue;
+	}
+
+	// "If node is a Text node and property name does not compute to new value
+	// on its parent:"
+	if (node.nodeType == Node.TEXT_NODE
+	&& !cssValuesEqual(propertyName, newValue, getComputedStyle(node.parentNode)[propertyName])) {
+		// "Let new parent be the result of calling createElement("span") on
+		// the ownerDocument of node."
+		var newParent = node.ownerDocument.createElement("span");
+
+		// "Set property name to new value on new parent."
+		newParent.style[propertyName] = newValue;
+
+		// "Insert new parent into node's parent before node."
+		node.parentNode.insertBefore(newParent, node);
+
+		// "Append node as the last child of new parent."
+		newParent.appendChild(node);
+	}
+}
+
+// "When a user agent is to unstyle a Range range, it must decompose the Range,
+// then unstyle each Node in the returned list."
+function unstyleRange(range, propertyName, propertyValue, tagList) {
+	var nodeList = decomposeRange(range);
+
+	for (var i = 0; i < nodeList.length; i++) {
+		unstyleNode(nodeList[i], propertyName, propertyValue, tagList);
+	}
+}
+
+function myExecCommand(commandId, showUI, value) {
+	commandId = commandId.toLowerCase();
+	var range = activeRange(document);
+
+	if (!range) {
+		return;
+	}
+
+	switch (commandId) {
+		case "bold":
+		if (getState("bold", range)) {
+			unstyleRange(range, "fontWeight", "normal", ["b", "strong"]);
+		} else {
+			styleRange(range, "fontWeight", "bold", ["b", "strong"]);
+		}
+		break;
+
+		case "createlink":
+		// "If value is the empty string, do nothing."
+		if (value === "") {
+			break;
+		}
+
+		// "Let node list be the result of decomposing the Range."
+		var nodeList = decomposeRange(range);
+
+		// "For each node in node list, in order:"
+		for (var i = 0; i < nodeList.length; i++) {
+			var node = nodeList[i];
+
+			// "Let text nodes be a list of all Text node descendants of node,
+			// or node itself if it's a Text node."
+			var textNodes = [];
+			if (node.nodeType == Node.TEXT_NODE) {
+				textNodes.push(node);
+			} else {
+				for (var cur = node.firstChild;
+				cur && cur.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINS;
+				cur = nextNode(cur)) {
+					if (cur.nodeType == Node.TEXT_NODE) {
+						textNodes.push(cur);
+					}
+				}
+			}
+
+			// "For each text node in text nodes, in tree order:"
+			for (var j = 0; j < textNodes.length; j++) {
+				var textNode = textNodes[j];
+
+				// "Let ancestor link be the parent of text node."
+				var ancestorLink = textNode.parentNode;
+
+				// "While ancestor link is not an HTML element, or its local
+				// name is not "a", or it has no HTML attribute with local name
+				// "href":"
+				while (ancestorLink.namespaceURI != htmlNamespace
+				|| ancestorLink.nodeType != Node.ELEMENT_NODE
+				|| ancestorLink.tagName != "A"
+				|| !ancestorLink.hasAttribute("href")) {
+					// "If the parent of ancestor link is not an Element, set
+					// ancestor link to null and break from this loop."
+					if (!ancestorLink.parentNode
+					|| ancestorLink.parentNode.nodeType != Node.ELEMENT_NODE) {
+						ancestorLink = null;
+						break;
+					}
+
+					// "Otherwise, set ancestor link to its parent."
+					ancestorLink = ancestorLink.parentNode;
+				}
+
+				// "If ancestor link is not null, set its "href" attribute to
+				// value and continue with the next text node."
+				if (ancestorLink) {
+					ancestorLink.setAttribute("href", value);
+					continue;
+				}
+
+				// "Let new parent be a new HTML element with local name "a",
+				// the same ownerDocument as text node, and a single HTML
+				// attribute with local name "href" and value value."
+				var newParent = textNode.ownerDocument.createElement("a");
+				newParent.setAttribute("href", value);
+
+				// "Insert new parent into text node's parent as the previous
+				// sibling of text node."
+				textNode.parentNode.insertBefore(newParent, textNode);
+
+				// "Append text node to new parent as its last child."
+				newParent.appendChild(textNode);
+			}
+		}
+
+		case "foreColor":
+		// Hacky test to see if the color is valid
+		var testEl = document.createElement("span");
+		testEl.style.color = value;
+		if (testEl.style.color === "") {
+			return;
+		}
+		styleRange(range, "color", value, []);
+		break;
+
+		case "italic":
+		if (getState("italic", range)) {
+			unstyleRange(range, "fontStyle", "normal", ["i", "em"]);
+		} else {
+			styleRange(range, "fontStyle", "italic", ["i", "em"]);
+		}
+		break;
+
+		default:
+		break;
+	}
+}
+
+function myQueryCommandState(commandId) {
+	commandId = commandId.toLowerCase();
+	var range = activeRange(document);
+
+	if (!range) {
+		return false;
+	}
+
+	return getState(commandId, range);
+}
+
+function getState(commandId, range) {
+	var style = getComputedStyle(beginningElement(range));
+
+	switch (commandId) {
+		case "bold":
+		return style.fontWeight == "bold"
+			|| (/^[0-9]+$/.test(style.fontWeight) && style.fontWeight >= 700);
+
+		case "italic":
+		return style.fontStyle == "italic" || style.fontStyle == "oblique";
+
+		default:
+		return false;
+	}
+}
+
+function myQueryCommandValue(commandId) {
+	commandId = commandId.toLowerCase();
+	var range = activeRange(document);
+
+	if (!range) {
+		return "";
+	}
+
+	var style = getComputedStyle(beginningElement(range));
+
+	switch (commandId) {
+		case "backcolor":
+		// "Let element be the beginning element of the Range."
+		var element = beginningElement(range);
+		// "While the computed style of "background-color" on element is any
+		// fully transparent value, set element to its parent."
+		while (element.nodeType == Node.ELEMENT_NODE
+		&& (getComputedStyle(element).backgroundColor == "rgba(0, 0, 0, 0)"
+		|| getComputedStyle(element).backgroundColor === ""
+		|| getComputedStyle(element).backgroundColor == "transparent")) {
+			element = element.parentNode;
+		}
+		// "Return the computed style of "background-color" for element."
+		if (element.nodeType != Node.ELEMENT_NODE) {
+			return 'rgb(255, 255, 255)';
+		}
+		return getComputedStyle(element).backgroundColor;
+
+		case "fontname":
+		return style.fontFamily;
+
+		case "forecolor":
+		return style.color;
+
+		default:
+		return "";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/notes.txt	Tue Mar 15 16:00:48 2011 -0600
@@ -0,0 +1,68 @@
+vBulletin (http://www.vbulletin.com/forum/clientscript/vbulletin_textedit.js):
+
+* Lots and lots and lots of browser sniffing.  Only appears to support IE,
+  Firefox, Opera.
+* Forces CSS mode off: if (is_moz) this.editdoc.execCommand('useCSS', false, true);
+* Wraps insertLink to unlink the selection first
+* Wraps various commands to insert HTML at the end of the document if
+  there's no selection, instead of (I guess) doing nothing
+* Consistently uses getRangeAt(0) to get the corresponding range, as though it
+  assumes that's the active range; in fact the affected range will be the last
+  one, typically, not the first, if there are multiple ranges.  Also uses
+  addRange() as though it will change the selection instead of expanding it.
+  This combination seems likely to cause some bugs in Firefox.  They mostly use
+  their own add_range() function as a Selection.selectAllChildren() workalike,
+  but in at least one place they use straight addRange(), probably by mistake.
+* Assumes all nodes are either element or text, in at least one place
+* Goes to some trouble to do insertImage but then add attributes; maybe this is
+  worth natively supporting somehow?  Something like insertNode?  (Or does that
+  already exist?)
+* Constantly calls a function check_focus(), which mostly just does
+  this.editwin.focus().  This is most likely because some commands fail
+  gratuitously if the document isn't focused?  Need to look into it.
+* When doing queryCommandValue('fontname'), special-cases the empty string by
+  replacing it with this.editdoc.body.style.fontFamily.  Similar for fontsize.
+* Has an entire function whose description is "Function to translate the output
+  from queryCommandState('forecolor') into something useful", which handles not
+  only the empty string and null, but also translates rgb() syntax to #xxxxxx
+  syntax ("translate_silly_hex()").  This one seems to be used only for setting
+  id's, though.
+
+jwysiwyg (https://github.com/akzhan/jwysiwyg):
+
+* Browser-sniffs "IE" vs. "W3C" by going down standards-compliant code-paths if
+  (window.getSelection).  jwysiwyg++.  (Mostly.)
+* Assumes window.getSelection evaluates to boolean false if there is no
+  selection.  I'm about 98% sure this is wrong.  But it just means the codepath
+  is always missed, which is harmless.
+* Also goes to some lengths to insert an image with attributes.  It does
+  insertImage with a src of "#jwysiwyg#", then effectively does
+  querySelector("img[src=#jwysiwyg#]") to find it, except it uses a home-made
+  getElementByAttributeValue method.
+* Also assumes getSelection().getRangeAt(0) will always return the current
+  selection's range.
+* Works around lack of insertHTML support in IE by using insertImage, finding
+  the image as described above, and replacing it with the desired HTML . . .
+  (uses browser-sniffing instead of feature-detection, bad jwysiwyg).
+* Throws in focus() calls for IE.  Maybe IE doesn't work right if stuff isn't
+  focused?
+* removeFormat doesn't remove headings, so it optionally removes headings when
+  its internal formatting thing is called . . . using
+  execCommand("formatBlock", false, "<p>").  ???
+* Has an option to replace <div> with <p>.  Wonder how well this works.
+* Turns styleWithCSS/useCSS off in Mozilla (although perhaps buggily).
+
+CKEditor (http://ckeditor.com): Spent a while trying to figure it out, gave up.
+Way too much abstraction for me to figure out.  I'm not sure if they use
+execCommand() at all.
+
+TinyMCE (http://tinymce.moxiecode.com/):
+
+* Big, complicated project, unlike vBulletin and jwysiwyg.  No way I'll be able
+  to even skim all of it.
+* Always forces styleWithCSS to false
+* "Text formatter engine class. This class is used to apply formats like bold,
+  italic, font size etc to the current selection or specific nodes. This engine
+  was build to replace the browsers default formatting logic for execCommand
+  due to it's inconsistant and buggy behavior."  Not much more to see here, it
+  seems.
--- a/preprocess	Wed Mar 02 12:17:54 2011 -0700
+++ b/preprocess	Tue Mar 15 16:00:48 2011 -0600
@@ -13,7 +13,10 @@
     "bpnode": "<span data-anolis-spec=domrange title=concept-boundary-point-node>node</span>",
     "bpoffset": "<span data-anolis-spec=domrange title=concept-boundary-point-offset>offset</span>",
     "bpposition": "<span data-anolis-spec=domrange title=concept-bp-position>position</span>",
+    "child": "<span data-anolis-spec=domcore title=concept-tree-child>child</span>",
+    "children": "<span data-anolis-spec=domcore title=concept-tree-child>children</span>",
     "collection": "<span data-anolis-spec=domcore title=concept-collection>collection</span>",
+    "contained": "<span data-anolis-spec=domrange>contained</span>",
     "comment": "<code data-anolis-spec=domcore>Comment</code>",
     "contextobject": "<span data-anolis-spec=domrange>context object</span>",
     "descendant": "<span data-anolis-spec=domcore title=concept-tree-descendant>descendant</span>",
@@ -24,9 +27,15 @@
     "index": "<span data-anolis-spec=domrange title=concept-indexof>index</span>",
     "localname": "<span data-anolis-spec=domcore title=concept-element-local-name>local name</span>",
     "namespace": "<span data-anolis-spec=domcore title=concept-element-namespace>namespace</span>",
+    "nextsibling": "<code data-anolis-spec=domcore title=dom-Node-nextSibling>nextSibling</code>",
     "node": "<code data-anolis-spec=domcore>Node</code>",
     "nodelength": "<span data-anolis-spec=domrange title=concept-node-length>length</span>",
     "ownerdocument": "<code data-anolis-spec=domcore title=dom-Node-ownerDocument>ownerDocument</code>",
+    "parent": "<span data-anolis-spec=domcore title=concept-tree-parent>parent</span>",
+    "partiallycontained": "<span data-anolis-spec=domrange>partially contained</span>",
+    "phrasingcontent": "<span data-anolis-spec=html>phrasing content</span>",
+    "presentationalhint": "<span data-anolis-spec=html title='presentational hints'>presentational hint</span>",
+    "previoussibling": "<code data-anolis-spec=domcore title=dom-Node-previousSibling>previousSibling</code>",
     "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>",
--- a/source.html	Wed Mar 02 12:17:54 2011 -0700
+++ b/source.html	Tue Mar 15 16:00:48 2011 -0600
@@ -3,7 +3,7 @@
 <title>HTML Editing Commands</title>
 <link rel=stylesheet href=http://www.whatwg.org/style/specification>
 <style>
- pre, code { font-family:monospace, sans-serif; }
+ pre, code, xmp { font-family:monospace, sans-serif; }
  h2 code, h3 code, h4 code,
  h2 :link, h3 :link, h4 :link,
  h2 :visited, h3 :visited, h4 :visited
@@ -16,6 +16,13 @@
      text-transform: uppercase;
    }
  }
+ xmp {
+   font-size: inherit;
+   font-variant: normal;
+   margin-left: 2em;
+   white-space: pre-wrap;
+ }
+ div.note > p:first-child::before { content: 'Note: '; }
 </style>
 <body class=draft>
 <div class=head id=head>
@@ -63,39 +70,38 @@
 <h2>Issues</h2>
 
 <ul>
-  <li><p>The styling/unstyling algorithms produce unreasonably messy DOMs.  I
-  don't know how far I'll go to simplify the output, but at the very least, I
-  need to wrap continguous phrasing content in a single tag, and be more
-  aggressive about splitting up existing tags instead of nesting extra tags
-  inside (e.g., unbolding the middle letter of <code
-  title>&lt;b>abc&lt;/b></code> should become <code
-  title>&lt;b>a&lt;/b>b&lt;b>c&lt;/b></code> instead of <code
-  title>&lt;b>a&lt;span style=font-weight:normal>b&lt;/span>c&lt;/b></code>).
-
-  <li><p>Need to make CSS terminology more precise, about setting/unsetting CSS
+  <li>Need to make CSS terminology more precise, about setting/unsetting CSS
   properties.  The intent is to modify the style attribute, CSSOM-style.
   Likewise, CSS value comparisons need to be done after serializing both
   values, so "bold" == "700" and "red" == "#f00" and so on.
 
-  <li><p>Also not sure about computed style.  There are differences between
+  <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><p>The wording I use for DOM stuff is a bit of a mess, often either
+  <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><p>I haven't put any thought yet into collapsed ranges or selections.
+  <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><p>I also don't pay attention to what happens to the selection when you
+  <li>I also don't pay attention to what happens to the selection when you
   mutate the DOM.  This is essential.
 
-  <li><p>JavaScript can modify the DOM synchronously in some cases, such as DOM
+  <li>JavaScript can modify the DOM synchronously in some cases, such as DOM
   mutation events and onunload when moving around iframes and objects.  This
   has to be dealt with somehow.  (Pointed out by Ryosuke Niwa of WebKit: <a
   href=http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2011-March/030730.html>1</a>
   <a href=http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2011-March/030751.html>2</a>)
+
+  <li>I'm sloppy about handling things like nodes that don't descend from a
+  Document, comments that are children of a Document, that sort of thing.  Not
+  essential for prototyping, but needs to be cleaned up eventually.
+
+  <li>I don't pay attention to whether designMode/contenteditable is actually
+  set.  I should be doing things like not doing anything if the selection isn't
+  editable, making sure not to break out of contenteditable regions, etc.
 </ul>
 
 
@@ -107,65 +113,43 @@
 <span data-anolis-spec=domcore title=concept-attr-namespace>namespace</span> is
 the [[htmlnamespace]].
 
-<p>The <dfn>first node</dfn> of a [[range]] is the [[node]] returned by the
-following algorithm:
+<p>The <dfn>beginning element</dfn> of a [[range]] is returned by the following
+algorithm:
 
 <ol>
-  <li><p>Let <var>range</var> be the [[range]] under discussion.
-
-  <li><p>If <var>range</var>'s [[rangestart]] [[bpoffset]] is equal to
-  the [[nodelength]] of its [[rangestart]] [[bpnode]], return the first
-  [[node]] that is after the [[rangestart]] [[bpnode]] and all its
-  [[descendants]] (if any) in [[treeorder]].  If there is no such [[node]],
-  return the last [[node]] in the document.
-
-  <li><p>If <var>range</var>'s [[rangestart]] [[bpnode]] has children,
-  return the child whose [[index]] is equal to <var>range</var>'s
-  [[rangestart]] [[bpoffset]].
-
-  <li><p>Return <var>range</var>'s [[rangestart]] [[bpnode]].
-</ol>
-
-<p>The <dfn>last node</dfn> of a [[range]] is the [[node]] returned by the
-following algorithm:
+  <li>If the [[rangestart]] [[bpnode]] of the [[range]] is a [[text]],
+  [[comment]], or [[processinginstruction]] node, and the [[rangestart]]
+  [[bpoffset]] of the [[range]] is not equal to the [[nodelength]] of its
+  [[rangestart]] [[bpnode]], let <var>first node</var> be the [[range]]'s
+  [[rangestart]] [[bpnode]].
 
-<ol>
-  <li><p>Let <var>range</var> be the [[range]] under discussion.
-
-  <li><p>If <var>range</var>'s [[rangeend]] [[bpoffset]] is zero, return the
-  last [[node]] that is before the [[rangeend]] [[bpnode]] in [[treeorder]].
-  If there is no such [[node]], return the first [[node]] in the document.
+  <li>Otherwise, let <var>first node</var> be the first [[node]] in
+  [[treeorder]] that is [[contained]] in the [[range]], if there is any.
 
-  <li><p>If <var>range</var>'s [[rangeend]] [[bpnode]] has children,
-  return the child whose [[index]] is equal to <var>range</var>'s
-  [[rangeend]] [[bpoffset]] minus one.
-
-  <li><p>Return <var>range</var>'s [[rangeend]] [[bpnode]].
-</ol>
+  <li>If <var>first node</var> is defined and is an [[element]], return
+  <var>first node</var>.
 
-<p>The <dfn>beginning element</dfn> of a [[range]] is its <span>first
-node</span> if that is an [[element]]; or the parent of its <span>first
-node</span>, if <em>that</em> is an [[element]]; or else null.
+  <li>Otherwise, if <var>first node</var> is defined and its [[parent]] is an
+  [[element]], return <var>first node</var>'s [[parent]].
 
-<p class=XXX>(It will be null only in weird cases, like selecting a comment
-whose parent is a document, or the child of a document fragment, or whatever.
-I'm ignoring those cases for now.)
+  <li>Return null.
+</ol>
 
 <p>The <dfn>active range</dfn> of a [[document]] is the value returned by the
 following algorithm:
 
 <ol>
-  <li><p>Let <var>selection</var> be the result of calling [[getselection]] on
+  <li>Let <var>selection</var> be the result of calling [[getselection]] on
   the [[document]].
 
-  <li><p>If there are no [[range]]s associated with <var>selection</var>,
+  <li>If there are no [[range]]s associated with <var>selection</var>,
   return null.
 
-  <li><p>Let <var>start</var> be the [[boundarypoint]] with the earliest
+  <li>Let <var>start</var> be the [[boundarypoint]] with the earliest
   [[bpposition]] among all of <var>selection</var>'s [[range]]s'
   [[rangestarts]].
 
-  <li><p>Return the last [[range]] in <var>selection</var> whose [[rangestart]]
+  <li>Return the last [[range]] in <var>selection</var> whose [[rangestart]]
   is <var>start</var>.
   <!-- This is what Firefox seems to do, no reason to change it . . . -->
 
@@ -173,348 +157,531 @@
   [[selection]], the active range is simply the only one in the selection.
 </ol>
 
+<p>Given a CSS property name <var>property name</var>, an (optional) value
+<var>property value</var> for that property, and a possibly empty list of
+strings <var>tag list</var>, a [[node]] is a <dfn>potentially relevant styling
+element</dfn> if it is an <span>HTML element</span> and one of the following
+holds:
+
+<ul>
+  <li>Its [[localname]] is in <var>tag list</var> and it has no attributes.
+
+  <li>Its [[localname]] is in <var>tag list</var> or is "span" or is "font",
+  and it has exactly one attribute, and that attribute is an <span>HTML
+  attribute</span> with [[attrlocalname]] "style", and that attribute sets
+  exactly one CSS property, and that property is <var>property name</var>, and
+  either <var>property value</var> is undefined or the value the attribute sets
+  the property to is <var>property value</var>.
+
+  <li>Its [[localname]] is "font", and it has exactly one attribute, and that
+  attribute is a <code data-anolis-spec=html title=dom-font-color>color</code>
+  attribute, and either <var>property value</var> is undefined or the effect of
+  the attribute is to hint that the CSS color attribute be set to <var>property
+  value</var>, and <var>property name</var> is "color".
+
+  <li>Its [[localname]] is "font", and it has exactly one attribute, and that
+  attribute is a <code data-anolis-spec=html title=dom-font-face>face</code>
+  attribute, and either <var>property value</var> is undefined or the effect of
+  the attribute is to hint that the CSS font-family attribute be set to
+  <var>property value</var>, and <var>property name</var> is "font-family".
+
+  <li>Its [[localname]] is "font", and it has exactly one attribute, and that
+  attribute is a <code data-anolis-spec=html title=dom-font-size>size</code>
+  attribute, and either <var>property value</var> is undefined or the effect of
+  the attribute is to hint that the CSS font-size attribute be set to
+  <var>property value</var>, and <var>property name</var> is "font-size".
+
+  <!-- Should we bother handling <font color=red style=color:red>?  Let's not.
+  -->
+</ul>
+
+<p>A [[node]] is a <dfn>relevant styling element</dfn> if it is a
+<span>potentially relevant styling element</span>, and its CSS property
+<var>property name</var> computes to <var>property value</var> (which cannot be
+undefined).
+
+<div class=note>
+<p>If <var>property name</var> is "font-weight", <var>property value</var> is
+"bold", and <var>tag list</var> contains "b", an example of a <span>potentially
+relevant styling element</span> that is not actually <span title="relevant
+styling element">relevant</span> is
+
+<xmp><p style="font-weight: 100"><b>Foo</b></p></xmp>
+
+<p>Since <code data-anolis-spec=html title="the b element">b</code>'s default
+font-weight is "bolder", the computed font-weight will most likely end up being
+"normal" or lighter.
+</div>
+
+<p>A <dfn>phrasing element</dfn> is either an <span>HTML element</span> that is
+categorized as [[phrasingcontent]], or a <span data-anolis-spec=html
+title="non-conforming element">non-conforming</span> <span>HTML element</span>
+(which thus has no categories), or an [[element]] that is not an <span>HTML
+element</span>.
+
+<p class=XXX>We should allow unrecognized HTML elements too.
+
+<p>The <dfn>specified style</dfn> of an [[element]] for a given <var>property
+name</var> is returned by the following algorithm, which will return either a
+CSS value or null:
+
+<ol>
+  <li>If the [[element]] has a <code data-anolis-spec=html
+  title="the style attribute">style</code> attribute set, and that attribute has
+  the effect of setting <var>property name</var>, return the value that it sets
+  <var>property name</var> to.
+
+  <li>If the [[element]] is a <code data-anolis-spec=html>font</code> element
+  that has an attribute whose effect is to create a [[presentationalhint]] for
+  <var>property name</var>, return the value that the hint sets <var>property
+  name</var> to.
+
+  <li>If the [[element]] is in the following list, and <var>property name</var>
+  is equal to the CSS property name listed for it, return the string listed for
+  it.
+
+  <p class=XXX>Add any other elements that can be output by the style/unstyle
+  algorithms, or existing browser implementations of execCommand().
+
+  <dl class=switch>
+    <dt><code data-anolis-spec=html title="the b element">b</code>
+    <dt><code data-anolis-spec=html title="the strong element">strong</code>
+    <dd>font-weight: "bold"
+
+    <dt><code data-anolis-spec=html title="the i element">i</code>
+    <dt><code data-anolis-spec=html title="the em element">em</code>
+    <dd>font-style: "italic"
+
+    <dt><code data-anolis-spec=html title="the u element">u</code>
+    <dd>text-decoration: "underline"
+  </dl>
+
+  <li>Return null.
+</ol>
+
 <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 [[node]]s 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>Decomposing a Range into Nodes</h2>
-<p>When a user agent is to <dfn>decompose a [[range]]</dfn> <var
-title>range</var>, it must run the following steps.
-
-<p class=note>The algorithm returns a list of [[node]]s in the [[range]] that
-are not contained in any other [[node]] in the [[range]].  It splits [[text]]
-nodes if necessary, but isn't picky about [[comment]]s or
-[[processinginstruction]]s.
+<p>When a user agent is to <dfn>decompose a [[range]]</dfn> <var>range</var>,
+it must run the following steps.
 
 <ol>
-  <li><p>Let <var>start node</var>, <var>start offset</var>, <var
-  title>end node</var>, and <var>end offset</var> be the [[rangestart]]
-  and [[rangeend]] [[bpnodes]] and [[bpoffsets]] of <var>range</var>,
-  respectively.
-
-  <li><p>If <var>start node</var> or <var>end node</var> is not an
-  [[element]], [[text]], [[processinginstruction]], or [[comment]] node, or is
-  not an [[element]] and has no parent, abort these steps.
-
-  <p class=XXX>Figure out something sensible here.
+  <li>If <var>range</var>'s [[rangestart]] and [[rangeend]] are the same,
+  return an empty list.
 
-  <li><p>If <var>start node</var> and <var>end node</var> are both
-  [[text]] nodes, and <var>start node</var> is the same as <var>end
-  node</var>, and neither <var>start offset</var> nor <var>end
-  offset</var> is equal to 0 or the [[nodelength]] of <var>start
-  node</var>:
+  <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><p>Run <code data-anolis-spec=domcore
-    title=dom-Text-splitText>splitText(<var>start offset</var>)</code> on
-    <var>start node</var> and set <var>start node</var> to the
-    result.
+    <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><p>Run <code data-anolis-spec=domcore
-    title=dom-Text-splitText>splitText(<var>end offset</var> &minus; <var
-    title>start offset</var>)</code> on <var>start node</var>.
+    <li>Set <var>end node</var> to <var>start node</var>.
 
-    <li><p>Return the list consisting of the single [[node]] <var>start
-    node</var>, and abort these steps.
+    <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><p>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>, run <code data-anolis-spec=domcore
-  title=dom-Text-splitText>splitText(<var>start offset</var>)</code> on
-  <var>start node</var> and set <var>start node</var> to the
-  returned node.  Set <var>start offset</var> to 0.
-
-  <li><p>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>.
-
-  <li><p>If <var>start offset</var> is nonzero and equals the [[nodelength]] of
-  <var>start node</var>, let <var>node</var> be the first [[node]] after
-  <var>start node</var> in [[treeorder]].  If there is no such [[node]], return
-  the empty list.
-
-  <li><p>Otherwise, if <var>start node</var> is an [[element]] with at least
-  one child, let <var>node</var> be the <var>start offset</var>th child of
-  <var>start node</var>.
-
-  <li><p>Otherwise, let <var>node</var> be <var>start node</var>.
-
-  <li><p>If <var>end node</var> is an [[element]] and <var>end
-  offset</var> is not 0, let <var>end</var> be the child of <var
-  title>end node</var> with [[index]] <var>end offset</var> &minus; 1.
-
-  <li><p>Otherwise, if <var>end offset</var> is 0, let <var
-  title>end</var> be the first [[node]] before <var>end node</var> in
-  [[treeorder]].  If there is no such [[node]], return the empty list.
-
-  <li><p>Otherwise, let <var>end</var> be <var>end node</var>.
-
-  <!-- We try to include a node's parent instead of that node if possible,
-  because this generally reduces the number of nodes we're handling.  So if the
-  string "oo bar" was selected in <b>Foo <i>bar</i></b>, we'd add the <i> to
-  the selection, even if the browser registered the end as the text node "bar".
-  -->
-  <li><p>While <var>node</var> is the first child of its parent and <var
-  title>end</var> is not a [[descendant]] of <var>node</var>'s parent,
-  set <var>node</var> to <var>node</var>'s parent.
-
-  <li><p>While <var>end</var> is the last child of its parent and <var
-  title>node</var> is not a [[descendant]] of <var>end</var>'s parent,
-  set <var>end</var> to <var>node</var>'s parent.
-
-  <li><p>Let <var>node list</var> be an empty list of [[node]]s.
-
-  <li><p>While <var>node</var> is not after <var>end</var> in
-  [[treeorder]]:
+  <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>:
 
   <ol>
-    <li><p>Append <var>node</var> to <var>node list</var>.
+    <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><p>Set <var>node</var> to the first [[node]] in [[treeorder]] that is
-    after <var>node</var> and all its [[descendants]] (if any).  If no such
-    [[node]] exists, break out of these substeps.
-
-    <li><p>While <var>node</var> is an [[ancestor]] of <var
-    title>end</var>, set <var>node</var> to <var>node</var>'s first child.
+    <li>Set <var>start offset</var> to 0.
   </ol>
 
-  <li><p>Return <var>node list</var>.
+  <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>Return a list consisting of every [[node]] [[contained]] in
+  <var>range</var> in [[treeorder]], omitting any whose [[parent]] is also
+  [[contained]] in <var>range</var>.
 </ol>
 
 
-<h2>Unstyling an element</h2>
-<p>When a user agent is to <dfn>unstyle an element</dfn>, it must run the
-following steps.
+<h2>Clearing an element's styles</h2>
+<p>When a user agent is to <dfn>clear styles</dfn> on an element, it must run
+the following steps:
+
+<div class=note>
+<p>Clearing styles (<span title="recursively clear styles">recursively</span>
+or not) can remove it from its parent and put other nodes in its place.  When
+implementations do something like clear style on all children of an element,
+they should take care not to assume that the set of children won't change as
+they're unstyled.  If the element is removed, the algorithm will return the
+list of nodes inserted in its place.
+
+<p>Clearing styles only removes inline styles from the element.  It doesn't
+ensure that the element isn't inheriting styles from an ancestor (or a style
+rule).  For that, one must <span>unstyle a node</span>.
+</div>
 
 <ol>
-  <li><p>Let <var>element</var> be the [[element]] to be unstyled.
+  <li>Let <var>element</var> be the [[element]] to be unstyled.
 
-  <li><p>Let <var>property name</var> and <var>tag list</var> be as
+  <li>Let <var>property name</var> and <var>tag list</var> be as
   in the invoking algorithm.
 
-  <li><p>Let <var>element children</var> be the [[element]] children of
-  <var>element</var>.
-
-  <li><p><span title="unstyle an element">Unstyle</span> each [[element]] in
-  <var>element children</var>, in order.
-
-  <p class=note>Unstyling an element can change the number of children its
-  parent has, so the list of children to unstyle needs to be computed
-  beforehand.
-
-  <li><p>If either
-
-  <ul>
-    <li><p><var>element</var> is an <span>HTML element</span> with
-    [[localname]] either "span" or in <var>tag list</var>, and it has
-    only a single attribute, and that attribute is named "style", and
-    that style attribute sets only the CSS property <var>property
-    name</var>; or
-
-    <li><p><var>element</var> is an <span>HTML element</span> with
-    [[localname]] in <var>tag list</var> and it has no attributes,
-  </ul>
-
-  <p>then:
+  <li>If <var>element</var> is a <span>potentially relevant styling
+  element</span>:
 
   <ol>
-    <li><p>Let <var>children</var> be an empty list of [[node]]s.
+    <li>Let <var>children</var> be an empty list of [[node]]s.
 
-    <li><p>While <var>element</var> has children:
+    <li>While <var>element</var> has children:
 
     <ol>
-      <li><p>Let <var>child</var> be the first child of <var>element</var>.
+      <li>Let <var>child</var> be the first child of <var>element</var>.
 
-      <li><p>Append <var>child</var> to <var>children</var>.
+      <li>Append <var>child</var> to <var>children</var>.
 
-      <li><p>Insert <var>child</var> as the previous sibling of
+      <li>Insert <var>child</var> as the previous sibling of
       <var>element</var>.
     </ol>
 
-    <li><p>Remove <var>element</var>.
+    <li>Remove <var>element</var>.
 
-    <li><p>Return <var>children</var>.
+    <li>Return <var>children</var>.
   </ol>
 
-  <li><p>Unset the CSS property <var>property name</var> of <var>element</var>.
+  <li>Unset the CSS property <var>property name</var> of <var>element</var>.
 
-  <li><p>If <var>element</var> is not an <span>HTML element</span> or its
+  <li>If <var>element</var> is a <code data-anolis-spec=html>font</code>
+  element:
+
+  <ol>
+    <li>If <var>property name</var> is "color", unset <var>element</var>'s
+    <code data-anolis-spec=html title=dom-font-color>color</code> attribute, if
+    set.
+
+    <li>If <var>property name</var> is "font-family", unset
+    <var>element</var>'s <code data-anolis-spec=html
+    title=dom-font-face>face</code> attribute, if set.
+
+    <li>If <var>property name</var> is "font-size", unset <var>element</var>'s
+    <code data-anolis-spec=html title=dom-font-size>size</code> attribute, if
+    set.
+  </ol>
+
+  <li>If <var>element</var> is not an <span>HTML element</span> or its
   [[localname]] is not in <var>tag list</var>, return the empty list.
+  <!-- If we get past this step, we're something like <b class=foo> where we
+  want to keep the extra attributes, so we stick them on a span. -->
 
-  <li><p>Let <var>new element</var> be a new <span>HTML element</span> with
+  <li>Let <var>new element</var> be a new <span>HTML element</span> with
   [[localname]] "span", with the same attributes and [[ownerdocument]] as
   <var>element</var>.
 
-  <li><p>Append <var>new element</var> to <var>element</var>'s
+  <li>Append <var>new element</var> to <var>element</var>'s
   parent as the previous sibling of <var>element</var>.
 
-  <li><p>While <var>element</var> has children, append its first child
+  <li>While <var>element</var> has children, append its first child
   as the last child of <var>new element</var>.
 
-  <li><p>Remove <var>element</var>.
+  <li>Remove <var>element</var>.
 
-  <li><p>Return the one-[[node]] list consisting of <var>new element</var>.
+  <li>Return the one-[[node]] list consisting of <var>new element</var>.
+</ol>
+
+
+<h2>Recursively clearing an element's styles</h2>
+<p>When a user agent is to <dfn>recursively clear styles</dfn> on an element,
+it must run the following steps:
+
+<ol>
+  <li>Let <var>element</var> be the [[element]] to be unstyled.
+
+  <li>Let <var>property name</var> and <var>tag list</var> be as
+  in the invoking algorithm.
+
+  <li>Let <var>element children</var> be the [[element]] children of
+  <var>element</var>.
+
+  <li><span>Recursively clear styles</span> on each [[element]] in <var>element
+  children</var>.
+
+  <li><span>Clear styles</span> on <var>element</var>, and return the resulting
+  list.
+</ol>
+
+
+<h2>Styling a Node</h2>
+<p>When a user agent is to <dfn>style a [[node]]</dfn> <var>node</var>, it must
+run the following steps.  There are three inputs: a CSS property name
+<var>property name</var>, a new value <var>property value</var>, and a possibly
+empty list of strings <var>tag list</var>.
+
+<p class=note>This algorithm applies the given style to the node itself, but
+doesn't interfere with conflicting styles on its descendants.  <span
+title="recursively style a node">Recursive styling</span> removes conflicting
+styles from descendants first.
+
+<ol>
+  <li>If <var>node</var>'s [[parent]] is null, or if <var>node</var> is not an
+  [[element]], [[text]], [[comment]], or [[processinginstruction]] node, abort
+  this algorithm. <!-- XXX: What to do here? -->
+
+  <li>If <var>node</var> is an [[element]]:
+
+  <ol>
+    <li><span>Clear styles</span> on <var>node</var>, and let <var>new
+    nodes</var> be the result.
+
+    <li>For each <var>new node</var> in <var>new nodes</var>, <span
+    title="style a node">style <var>new node</var></span>, with the same inputs
+    as this invocation of the algorithm.
+
+    <li>If <var>node</var>'s [[parent]] is null, abort this algorithm.
+  </ol>
+
+  <li>If <var>node</var> is an [[element]] but not a <span>phrasing
+  element</span>:
+
+  <ol>
+    <li>Let <var>children</var> be all [[children]] of <var>node</var>,
+    omitting any that are [[element]]s whose <span>specified style</span> for
+    <var>property name</var> is neither null nor equal to <var>property
+    value</var>.
+
+    <li><span title="style a node">Style</span> each [[node]] in
+    <var>children</var>.
+
+    <li>Abort this algorithm.
+  </ol>
+
+  <li>If <var>node</var>'s [[previoussibling]] is a <span>relevant styling
+  element</span>, append <var>node</var> as the last [[child]] of its
+  [[previoussibling]] and abort this algorithm.
+
+  <li>If <var>node</var>'s [[nextsibling]] is a <span>relevant styling
+  element</span>, insert <var>node</var> as the first [[child]] of its
+  [[nextsibling]] and abort this algorithm.
+
+  <li>If <var>node</var> is a [[comment]] or [[processinginstruction]], abort
+  this algorithm.  <!-- There's no point in making a new element in this case.
+  -->
+
+  <li>If <var>node</var> is an [[element]] and the computed style of
+  <var>property name</var> for it is <var>property value</var>, abort this
+  algorithm.
+
+  <li>If <var>node</var> is a [[text]] node and the computed style of
+  <var>property name</var> for its [[parent]] is <var>property value</var>,
+  abort this algorithm.
+
+  <li>Let <var>tag</var> be the first string in <var>tag list</var>, if that is
+  not empty, or "span" if it is empty.
+
+  <li>Let <var>new parent</var> be the result of calling <code
+  data-anolis-spec=domcore
+  title=dom-Document-createElement>createElement(<var>tag</var>)</code> on the
+  [[ownerdocument]] of <var>node</var>.
+
+  <li>Insert <var>new parent</var> in <var>node</var>'s [[parent]] before
+  <var>node</var>.
+
+  <li>If the computed value of <var>property name</var> for <var>new
+  parent</var> is not <var>property value</var>, set the CSS property
+  <var>property name</var> of <var>new parent</var> to <var>property
+  value</var>.
+
+  <li>Append <var>node</var> to <var>new parent</var> as its last [[child]].
+</ol>
+
+
+<h2>Recursively styling a Node</h2>
+<p>When a user agent is to <dfn>recursively style a [[node]]</dfn>
+<var>node</var>, it must run the following steps.  There are three inputs: a
+CSS property name <var>property name</var>, a new value <var>property
+value</var>, and a possibly empty list of strings <var>tag list</var>.
+
+<ol>
+  <li>If <var>node</var>'s [[parent]] is null, or if <var>node</var> is not an
+  [[element]], [[text]], [[comment]], or [[processinginstruction]] node, abort
+  this algorithm. <!-- XXX: What to do here? -->
+
+  <li>If <var>node</var> is an [[element]]:
+
+  <ol>
+    <li><span>Recursively clear styles</span> on <var>node</var>, and let
+    <var>new nodes</var> be the result.
+
+    <li>For each <var>new node</var> in <var>new nodes</var>, <span
+    title="recursively style a node">recursively style <var>new
+    node</var></span>, with the same inputs as this invocation of the
+    algorithm.
+
+    <li>If <var>node</var>'s [[parent]] is null, abort this algorithm.
+  </ol>
+
+  <li><span title="style a node">Style</span> <var>node</var>.
 </ol>
 
 
 <h2>Styling a Range</h2>
-<p>When a user agent is to <dfn>style a [[range]]</dfn> <var>range</var>, it
+<p>When a user agent is to <dfn>style a [[range]]</dfn>, it must <span
+title="decompose a range">decompose</span> the [[range]], then <span
+title="recursively style a node">recursively style</span> each [[node]] in the
+returned list.
+
+
+<h2>Unstyling a Node</h2>
+<p>When a user agent is to <dfn>unstyle a [[node]]</dfn> <var>node</var>, it
 must run the following steps.  There are three inputs: a CSS property name
-<var>property name</var>, a new value <var>property value</var>, and a possibly
+<var>property name</var>, a new value <var>new value</var>, and a possibly
 empty list of strings <var>tag list</var>.
 
 <ol>
-  <li><p>Let <var>node list</var> be the result of <span title="decompose
-  a range">decomposing</span> <var>range</var>.
+  <li>If <var>node</var>'s [[parent]] is null, or if <var>node</var> is not an
+  [[element]] or [[text]] node, abort this algorithm. <!-- XXX: What to do
+  here?  We want to ignore comments and PIs, but we might want to support
+  detached elements, documents, document fragments, . . . -->
 
-  <li><p>For each <var>node</var> in <var>node list</var>, in order:
+  <li>If <var>node</var> is an [[element]]:
 
   <ol>
-    <li><p>If <var>node</var> is an <code
-    data-anolis-spec=domcore>Element</code>:
+    <li><span>Recursively clear styles</span> on <var>node</var>, and let
+    <var>new nodes</var> be the result.
+
+    <li>For each <var>new node</var> in <var>new nodes</var>, <span
+    title="unstyle a node">unstyle <var>new node</var></span>, with the same
+    inputs as this invocation of the algorithm.
+
+    <li>If <var>node</var>'s [[parent]] is null, abort this algorithm.
+  </ol>
+
+  <li>If <var>node</var> is an [[element]], let <var>current value</var> equal
+  the computed value of <var>property name</var> on <var>node</var>.
+  Otherwise, let <var>current value</var> equal the computed value of
+  <var>property name</var> on <var>node</var>'s [[parent]].
+
+  <li>If <var>current value</var> equals <var>new value</var>, abort this
+  algorithm.
+
+  <li>Let <var>ancestor list</var> be a list of [[node]]s, initially empty.
+
+  <li>Let <var>current ancestor</var> equal <var>node</var>.
+
+  <li>While <var>current ancestor</var>'s [[parent]] is an [[element]], set
+  <var>current ancestor</var> to its [[parent]], then append it to
+  <var>ancestor list</var>.
+  
+  <li>While <var>ancestor list</var> is not empty, and the last member of
+  <var>ancestor list</var> has <span>specified style</span> for <var>property
+  name</var> equal to <var>new value</var> or null, remove the last member from
+  <var>ancestor list</var>.
+
+  <li>While <var>ancestor list</var> is not empty:
+
+  <ol>
+    <li>Let <var>current ancestor</var> be the last member of <var>ancestor
+    list</var>.
+
+    <li>Remove the last member from <var>ancestor list</var>.
+
+    <li>Let <var>propagated value</var> be the <span>specified style</span> of
+    <var>current ancestor</var> for <var>property name</var>.
+
+    <li>If <var>propagated value</var> is null, continue this loop from the
+    beginning.
+
+    <li>Let <var>children</var> be the [[children]] of <var>current
+    ancestor</var>.
+
+    <li><span>Clear styles</span> on <var>current ancestor</var>.
+
+    <li>For every <var>child</var> in <var>children</var>:
 
     <ol>
-      <li><p>Unset the CSS property <var>property name</var> of <var
-      title>node</var>.
-
-      <li><p>If the computed value of <var>property name</var> for
-      <var>node</var> is not <var>property value</var>, set the CSS property
-      <var>property name</var> of <var>node</var> to <var>property value</var>.
-      <!-- This means we don't bother applying the property if the style is
-      already present, e.g., from an ancestor.  But we do apply it if the
-      element is the expected sort of element but the style is wrong anyway,
-      e.g., <span style=font-weight:100><b>Foo</b></span> where b's style is
-      font-weight: bold. -->
-
-      <li><p>Let <var>element children</var> be the [[element]] children
-      of <var>node</var>.
-
-      <li><p><span title="unstyle an element">Unstyle</span> each [[element]]
-      in <var>element children</var>, in order.
-
-      <p class=note>Unstyling an element can change the number of children its
-      parent has, so the list of children to unstyle needs to be computed
-      beforehand.
-    </ol>
-
-    <li><p>Otherwise, if <var>node</var> is a <code
-    data-anolis-spec=domcore>Text</code> node:
+      <li>If <var>child</var> is <var>node</var>, continue with the next
+      <var>child</var>.
 
-    <ol>
-      <li><p>Let <var>tag</var> be the first string in <var>tag list</var>, or
-      "span" if <var>tag list</var> is empty.
-
-      <li><p>Let <var>new parent</var> be the result of calling <code
-      data-anolis-spec=domcore
-      title=dom-Document-createElement>createElement(<var>tag</var>)</code> on
-      the [[ownerdocument]] of <var>node</var>.
-
-      <li><p>Append <var>new parent</var> to <var>node</var>'s parent as the
-      previous sibling of <var>node</var>.
+      <li>If <var>child</var> is an [[element]] whose <span>specified
+      style</span> for <var>property name</var> is neither null nor equal to
+      <var>propagated value</var>, continue with the next <var>child</var>.
 
-      <li><p>If the computed value of <var>property name</var> on <var>new
-      parent</var> is not equal to <var>property value</var>, set the CSS
-      property <var>property name</var> of <var>new parent</var> to
-      <var>property value</var>.
-      <!-- This is needed if tag list is empty, but also if the correct style
-      is being suppressed for some reason, like <span
-      style=font-weight:100><b>Foo</b></span> where b is font-weight: bolder.
-      -->
+      <li>If <var>child</var> is the last member of <var>ancestor list</var>,
+      set <var>child</var>'s CSS property <var>property name</var> to
+      <var>propagated value</var> and continue with the next <var>child</var>.
 
-      <li><p>Append <var>node</var> to <var>new parent</var> as
-      its last child.
+      <p class=note>This style will be removed on the next loop iteration and
+      distributed to its children.
+
+      <li><span title="style a node">Style</span> <var>child</var>, with
+      <var>property name</var> and <var>tag list</var> as in this algorithm,
+      and <var>property value</var> equal to <var>propagated value</var>.
     </ol>
+  </ol>
 
-    <li><p>Otherwise, do nothing.
+  <!-- We might have a rule inherited from someplace where we can't remove it,
+  or maybe even a rule in a stylesheet (although that case is pathological and
+  we generally ignore it) -->
+  <li>If <var>node</var> is an [[element]] and <var>property name</var> does
+  not compute to <var>new value</var> on it, set <var>property name</var> to
+  <var>new value</var> on it.
+
+  <li>If <var>node</var> is a [[text]] node and <var>property name</var> does
+  not compute to <var>new value</var> on its [[parent]]:
+
+  <ol>
+    <li>Let <var>new parent</var> be the result of calling <code
+    data-anolis-spec=domcore
+    title=dom-Document-createElement>createElement("span")</code> on the
+    [[ownerdocument]] of <var>node</var>.
+
+    <li>Set <var>property name</var> to <var>new value</var> on <var>new
+    parent</var>.
+
+    <li>Insert <var>new parent</var> into <var>node</var>'s [[parent]] before
+    <var>node</var>.
+
+    <li>Append <var>node</var> as the last [[child]] of <var>new parent</var>.
   </ol>
 </ol>
-<!-- Out of IE9, Gecko, WebKit, and Opera, when asked to (e.g.) bold an
-element, IE9 and WebKit and Opera wrap various descendants in <b> or <strong>;
-Gecko just adds a style attribute.  The latter is simpler, particularly because
-you then don't have to worry about making sure you only insert your tags in a
-valid place (which you have to so that text/html serialization is possible, if
-nothing else).  I originally specced the former approach, available in git
-history. -->
 
 
 <h2>Unstyling a Range</h2>
 <p>When a user agent is to <dfn>unstyle a [[range]]</dfn> <var>range</var>, it
-must run the following steps.  There are three inputs: a CSS property name
-<var>property name</var>, a new value <var>property value</var>, and a possibly
-empty list of strings <var>tag list</var>.
-
-<ol>
-  <li><p>Let <var>node list</var> be the result of <span title="decompose
-  a range">decomposing</span> <var>range</var>.
-
-  <li><p>For each <var>node</var> in <var>node list</var>, in order:
-
-  <ol>
-    <li><p>If <var>node</var> is an <code
-    data-anolis-spec=domcore>Element</code>:
-
-    <ol>
-      <li><p>Let <var>extra nodes</var> be the result of <span
-      title="unstyle an element">unstyling</span> <var>node</var>.
-
-      <li><p>If <var>node</var> no longer has a parent:
-
-      <ol>
-        <li><p>Insert all the [[node]]s in <var>extra nodes</var> into
-        <var>node list</var> immediately after <var>node</var>, in order.
-
-        <li><p>Continue with the next [[node]] in <var>node list</var>, if any.
-
-        <p class=note>The next [[node]] will be the first in <var>extra
-        nodes</var>, unless <var>extra nodes</var> is empty.
-      </ol>
-
-      <li><p>If the computed value of <var>property name</var> for <var
-      title>node</var> is not <var>property value</var>, set the CSS
-      property <var>property name</var> of <var>node</var> to <var
-      title>property value</var>.
-
-      <li><p>Let <var>element children</var> be the [[element]] children
-      of <var>node</var>.
-
-      <li><p><span title="unstyle an element">Unstyle</span> each [[element]]
-      in <var>element children</var>, in order.
-
-      <p class=note>Unstyling an element can change the number of children its
-      parent has, so the list of children to unstyle needs to be computed
-      beforehand.
-    </ol>
-
-    <li><p>Otherwise, if <var>node</var> is a <code
-    data-anolis-spec=domcore>Text</code> node and the computed value of <var
-    title>property name</var> for <var>node</var>'s parent is not <var
-    title>property value</var>:
-
-    <ol>
-      <li><p>Let <var>new parent</var> be the result of calling <code
-      data-anolis-spec=domcore
-      title=dom-Document-createElement>createElement("span")</code> on the
-      [[ownerdocument]] of <var>node</var>.
-
-      <li><p>Set the CSS property <var>property name</var> of <var>new
-      parent</var> to <var>property value</var>.
-
-      <li><p>Insert <var>new parent</var> as <var>node</var>'s
-      previous sibling.
-
-      <li><p>Append <var>node</var> to <var>new parent</var> as its
-      child.
-    </ol>
-
-    <li><p>Otherwise, do nothing.
-  </ol>
-</ol>
+must <span title="decompose a range">decompose</span> the [[range]], then <span
+title="unstyle a node">unstyle</span> each [[node]] in the returned list.
 
 
 <h2>Commands</h2>
@@ -600,17 +767,17 @@
 <dd><p><strong>Value</strong>: The value is given by the following algorithm:
 
 <ol>
-  <li><p>Let <var>element</var> be the <span>beginning element</span> of the
+  <li>Let <var>element</var> be the <span>beginning element</span> of the
   [[range]].
 
-  <li><p>While the computed style of "background-color" on <var>element</var>
+  <li>While the computed style of "background-color" on <var>element</var>
   is any fully transparent value, set <var>element</var> to its parent.
 
   <p class=XXX>It's intended that for these purposes, the root element will
   have a white background.  Should that be specified somewhere?  I don't think
   all UAs actually do it.
 
-  <li><p>Return the computed style of "background-color" for
+  <li>Return the computed style of "background-color" for
   <var>element</var>.
 </ol>
 <!-- Chrome 10 returns rgba(0, 0, 0, 0) if there's no background defined
@@ -647,7 +814,7 @@
 <dd><p><strong>Action</strong>: The user agent must run the following steps:
 
 <ol>
-  <li><p>If <var>value</var> is the empty string, abort these steps and do
+  <li>If <var>value</var> is the empty string, abort these steps and do
   nothing.
   <!-- This matches Firefox 4b11 and Chrome 11 dev.  IE 9 RC and Opera 11 both
   treat the request literally.  Gecko and WebKit probably have it right here:
@@ -656,35 +823,35 @@
   specify "#" for the value, or the author can rewrite it, so it's not like
   this makes the API less useful. -->
 
-  <li><p>Let <var>node list</var> be the result of <span title="decompose a
+  <li>Let <var>node list</var> be the result of <span title="decompose a
   range">decomposing</span> the [[range]].
 
-  <li><p>For each <var>node</var> in <var>node list</var>, in order:
+  <li>For each <var>node</var> in <var>node list</var>, in order:
 
   <ol>
-    <li><p>Let <var>text nodes</var> be a list of all [[text]] node
+    <li>Let <var>text nodes</var> be a list of all [[text]] node
     [[descendants]] of <var>node</var>, or <var>node</var> itself if it's a
     [[text]] node.
 
-    <li><p>For each <var>text node</var> in <var>text nodes</var>, in
+    <li>For each <var>text node</var> in <var>text nodes</var>, in
     [[treeorder]]:
 
     <ol>
-      <li><p>Let <var>ancestor link</var> be the parent of <var>text
+      <li>Let <var>ancestor link</var> be the parent of <var>text
       node</var>.
 
-      <li><p>While <var>ancestor link</var> is not an <span>HTML
+      <li>While <var>ancestor link</var> is not an <span>HTML
       element</span>, or its [[localname]] is not "a", or it has no <span>HTML
       attribute</span> with [[attrlocalname]] "href":
 
       <ol>
-        <li><p>If the parent of <var>ancestor link</var> is not an [[element]],
+        <li>If the parent of <var>ancestor link</var> is not an [[element]],
         set <var>ancestor link</var> to null and break from this loop.
 
-        <li><p>Otherwise, set <var>ancestor link</var> to its parent.
+        <li>Otherwise, set <var>ancestor link</var> to its parent.
       </ol>
 
-      <li><p>If <var>ancestor link</var> is not null, set its "href" attribute
+      <li>If <var>ancestor link</var> is not null, set its "href" attribute
       to <var>value</var> and continue with the next <var>text node</var>.
       <!-- There are three approaches here.  For instance, if you ask browsers
       to create a link to "http://example.org" on the "b" here:
@@ -709,19 +876,19 @@
       miss out a character when selecting the link you want to change, do you
       really intend to only change the link of part of it? -->
 
-      <li><p>Let <var>new parent</var> be the result of calling <code
+      <li>Let <var>new parent</var> be the result of calling <code
       data-anolis-spec=domcore
       title=dom-Document-createElement>createElement("a")</code> on the
       [[ownerdocument]] of <var>text node</var>.
 
-      <li><p>Call <code data-anolis-spec=domcore
+      <li>Call <code data-anolis-spec=domcore
       title=dom-Element-setAttribute>setAttribute("href",
       <var>value</var>)</code> on <var>new parent</var>.
 
-      <li><p>Insert <var>new parent</var> into <var>text node</var>'s parent as
+      <li>Insert <var>new parent</var> into <var>text node</var>'s parent as
       the previous sibling of <var>text node</var>.
 
-      <li><p>Append <var>text node</var> to <var>new parent</var> as its last
+      <li>Append <var>text node</var> to <var>new parent</var> as its last
       child.
     </ol>
   </ol>
@@ -821,10 +988,8 @@
 
 <dd><p><strong>Action</strong>: The user agent must run the following steps:
 
-<p class=XXX>We need to delete the selection if it's not collapsed.
-
 <ol>
-  <li><p>If <var>value</var> is the empty string, abort these steps and do
+  <li>If <var>value</var> is the empty string, abort these steps and do
   nothing.
   <!-- Similar logic to createLink, except even more compelling, since an HTML
   document linking to itself as an image is just silly.  In fact, the current
@@ -832,27 +997,30 @@
   immediately if the URL is empty.  Firefox 4b11 bails out on an empty string,
   but the other three browsers I tested stick in the <img> anyway. -->
 
-  <li><p>Let (<var>node</var>, <var>offset</var>) be the [[range]]'s
+  <li>Run <code data-anolis-spec=domrange
+  title=dom-Range-deleteContents>deleteContents()</code> on the [[range]].
+
+  <li>Let (<var>node</var>, <var>offset</var>) be the [[range]]'s
   [[rangestart]].
 
-  <li><p>Let <var>img</var> be a new <span>HTML element</span> with
+  <li>Let <var>img</var> be a new <span>HTML element</span> with
   [[localname]] "img", the same [[ownerdocument]] as <var>node</var>, and a
   single <span>HTML attribute</span> with [[attrlocalname]] "src" and with
   [[attrvalue]] <var>value</var>.
   <!-- No alt text, so it's invalid.  This matches all browsers. -->
 
-  <li><p>If <var>node</var> is a [[text]] node, and <var>offset</var> is not
+  <li>If <var>node</var> is a [[text]] node, and <var>offset</var> is not
   equal to 0 or the [[nodelength]] of <var>node</var>, run <code
   data-anolis-spec=domcore
   title=dom-Text-splitText>splitText(<var>offset</var>)</code> on
   <var>node</var>.
 
-  <li><p>If <var>node</var> is a [[text]], [[comment]], or
+  <li>If <var>node</var> is a [[text]], [[comment]], or
   [[processinginstruction]] node, run <code data-anolis-spec=domcore
   title=dom-Node-insertBefore>insertBefore(<var>img</var>,
   <var>node</var>)</code> on the parent of <var>node</var>.
 
-  <li><p>Otherwise, let <var>child</var> be the <var>offset</var>th child of
+  <li>Otherwise, let <var>child</var> be the <var>offset</var>th child of
   <var>node</var> (or null if there is no such child), and run <code
   data-anolis-spec=domcore
   title=dom-Node-insertBefore>insertBefore(<var>img</var>,
@@ -896,27 +1064,27 @@
 what the other browsers do instead.
 
 <ol>
-  <li><p>Let <var>links</var> be a [[collection]] rooted at the [[range]]'s
+  <li>Let <var>links</var> be a [[collection]] rooted at the [[range]]'s
   [[rangeroot]], whose filter matches only <span title="HTML element">HTML
   elements</span> with [[localname]] "a", that have an <span>HTML
   attribute</span> with [[attrlocalname]] "href".
 
-  <li><p>Let <var>selected</var> be a [[collection]] rooted at the [[range]]'s
-  [[rangeroot]], whose filter matches only [[node]]s that are equal to or after
-  the [[range]]'s <span>first node</span> in [[treeorder]], and also equal to
-  or before the [[range]]'s <span>last node</span> in [[treeorder]].
+  <li>Let <var>selected</var> be a [[collection]] rooted at the [[range]]'s
+  [[rangeroot]], whose filter matches only [[node]]s that are [[contained]] in
+  the [[range]] and [[text]] nodes that are [[partiallycontained]] in the
+  [[range]].
 
-  <li><p>For each <var>link</var> in <var>links</var>, in [[treeorder]]:
+  <li>For each <var>link</var> in <var>links</var>, in [[treeorder]]:
 
   <ol>
-    <li><p>If <var>link</var> is not in <var>selected</var> and is not an
+    <li>If <var>link</var> is not in <var>selected</var> and is not an
     [[ancestor]] of any [[node]] in <var>selected</var>, continue with the next
     <var>link</var>.
 
-    <li><p>While <var>link</var> has children, insert <var>link</var>'s first
+    <li>While <var>link</var> has children, insert <var>link</var>'s first
     child into its parent as <var>link</var>'s previous sibling.
 
-    <li><p>Remove <var>link</var>.
+    <li>Remove <var>link</var>.
 
     <p class=XXX>This means it disappears even if it had an id, class, etc.
     Maybe not what we want?