Preliminary outdent, known issues
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Thu, 28 Apr 2011 15:01:23 -0600
changeset 73 e6eebb4728bf
parent 72 7e30b61d6ab9
child 74 75fbcc5872a0
Preliminary outdent, known issues
autoimplementation.html
editcommands.html
implementation.js
preprocess
source.html
--- a/autoimplementation.html	Thu Apr 28 12:54:21 2011 -0600
+++ b/autoimplementation.html	Thu Apr 28 15:01:23 2011 -0600
@@ -513,6 +513,12 @@
 		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
 		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
 		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar</p></blockquote><p>b]az</p><p>extra',
+
+		// Spec:
+		'<blockquote style="margin: 0 40px"><p>foo[bar]</p><p>baz</p></blockquote><p>extra',
+		'<blockquote style="margin: 0 40px"><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
+		'<blockquote style="margin: 0 40px"><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
+		'<blockquote style="margin: 0 40px"><p>foo[bar</p></blockquote><p>b]az</p><p>extra',
 	],
 	inserthorizontalrule: [
 		'foo[]bar',
@@ -605,6 +611,39 @@
 		'foo [bar <i>baz] qoz</i> quz sic',
 		'foo bar <i>baz [qoz</i> quz] sic',
 	],
+	outdent: [
+		// These mimic existing indentation in various browsers, to see how
+		// they cope with outdenting various things.  This is Gecko non-CSS and
+		// Opera:
+		'<blockquote><p>foo[bar]</p><p>baz</p></blockquote><p>extra',
+		'<blockquote><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
+		'<blockquote><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
+		'<blockquote><p>foo[bar</p></blockquote><p>b]az</p><p>extra',
+
+		// IE:
+		'<blockquote style="margin-right: 0" dir="ltr"><p>foo[bar]</p><p>baz</p></blockquote><p>extra',
+		'<blockquote style="margin-right: 0" dir="ltr"><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
+		'<blockquote style="margin-right: 0" dir="ltr"><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
+		'<blockquote style="margin-right: 0" dir="ltr"><p>foo[bar</p></blockquote><p>b]az</p><p>extra',
+
+		// Firefox CSS mode:
+		'<p style="margin-left: 40px">foo[bar]</p><p style="margin-left: 40px">baz</p><p>extra',
+		'<p style="margin-left: 40px">foo[bar</p><p style="margin-left: 40px">b]az</p><p>extra',
+		'<p style="margin-left: 40px">foo[bar]</p><p>baz</p><p>extra',
+		'<p style="margin-left: 40px">foo[bar</p><p>b]az</p><p>extra',
+
+		// WebKit:
+		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar]</p><p>baz</p></blockquote><p>extra',
+		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
+		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
+		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar</p></blockquote><p>b]az</p><p>extra',
+
+		// Spec:
+		'<blockquote style="margin: 0 40px"><p>foo[bar]</p><p>baz</p></blockquote><p>extra',
+		'<blockquote style="margin: 0 40px"><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
+		'<blockquote style="margin: 0 40px"><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
+		'<blockquote style="margin: 0 40px"><p>foo[bar</p></blockquote><p>b]az</p><p>extra',
+	],
 	removeformat: [
 		'foo[]bar',
 		'<span>foo</span>{}<span>bar</span>',
@@ -1241,6 +1280,10 @@
 		var normalizedSpecCell = tr.childNodes[1].lastChild.firstChild.textContent
 			.replace(/[[\]{}]/g, "")
 			.replace(/ style="margin: 0 40px"/g, "")
+			.replace(/ class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px;"/g, '')
+			.replace(/ style="margin-right: 0px;" dir="ltr"/g, '')
+			.replace(/ style="margin-left: 0px;" dir="rtl"/g, '')
+			.replace(/ style="margin-(left|right): 40px;"/g, '')
 			.replace(/: /g, ":")
 			.replace(/;? ?"/g, '"')
 			.replace(/<(\/?)strong/g, '<$1b')
@@ -1249,6 +1292,7 @@
 			.replace(/#[0-9a-fA-F]{6}/g, function(match) { return match.toUpperCase(); });
 		var normalizedBrowserCell = tr.childNodes[2].lastChild.firstChild.textContent
 			.replace(/[[\]{}]/g, "")
+			.replace(/ style="margin: 0 40px"/g, "")
 			.replace(/ class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px;"/g, '')
 			.replace(/ style="margin-right: 0px;" dir="ltr"/g, '')
 			.replace(/ style="margin-left: 0px;" dir="rtl"/g, '')
--- a/editcommands.html	Thu Apr 28 12:54:21 2011 -0600
+++ b/editcommands.html	Thu Apr 28 15:01:23 2011 -0600
@@ -59,11 +59,12 @@
  <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="#clearing-an-element's-value"><span class=secno>5 </span>Clearing an element's value</a></li>
- <li><a href=#pushing-down-values><span class=secno>6 </span>Pushing down values</a></li>
- <li><a href=#forcing-the-value-of-a-node><span class=secno>7 </span>Forcing the value of a node</a></li>
- <li><a href=#setting-the-value-of-a-node><span class=secno>8 </span>Setting the value of a node</a></li>
- <li><a href=#commands><span class=secno>9 </span>Commands</a></li>
+ <li><a href=#block-extending-a-range><span class=secno>5 </span>Block-extending a range</a></li>
+ <li><a href="#clearing-an-element's-value"><span class=secno>6 </span>Clearing an element's value</a></li>
+ <li><a href=#pushing-down-values><span class=secno>7 </span>Pushing down values</a></li>
+ <li><a href=#forcing-the-value-of-a-node><span class=secno>8 </span>Forcing the value of a node</a></li>
+ <li><a href=#setting-the-value-of-a-node><span class=secno>9 </span>Setting the value of a node</a></li>
+ <li><a href=#commands><span class=secno>10 </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-->
@@ -181,6 +182,9 @@
 <p>An <dfn id=html-element>HTML element</dfn> is an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-namespace title=concept-element-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>An <dfn id=inline-node>inline node</dfn> is either a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node, or an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> whose
+"display" property computes to "inline", "inline-block", or "inline-table".
+
 <p>Something is <dfn id=editable>editable</dfn> if either it is an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> with a <code class=external data-anolis-spec=html title=attr-contenteditable><a href=http://www.whatwg.org/html/#attr-contenteditable>contenteditable</a></code>
 attribute set to the true state; or it is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#document>Document</a></code> whose <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#designmode>designMode</a></code> is enabled; or it is a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> 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 <a href=#editable>editable</a>, but which does not have a <code class=external data-anolis-spec=html title=attr-contenteditable><a href=http://www.whatwg.org/html/#attr-contenteditable>contenteditable</a></code>
@@ -534,7 +538,74 @@
 </ol>
 
 
-<h2 id="clearing-an-element's-value"><span class=secno>5 </span>Clearing an element's value</h2>
+<h2 id=block-extending-a-range><span class=secno>5 </span>Block-extending a range</h2>
+<p>When a user agent is to <dfn id=block-extend>block-extend</dfn> a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>
+<var title="">range</var>, it must run the following steps:
+
+<p class=XXX>Surely I can come up with a better name.
+
+<ol>
+  <li>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>.
+
+  <li>Repeat the following steps:
+
+  <ol>
+    <li>If <var title="">start node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node or
+    <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> and then set <var title="">start node</var> to its
+    <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+    <li>Otherwise, if <var title="">start offset</var> 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 <var title="">start node</var>, set <var title="">start 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="">start node</var> and then set <var title="">start
+    node</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+    <li>Otherwise, if the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">start node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+    <var title="">start offset</var> minus one is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node, or an
+    (insert definition here), subtract one from <var title="">start offset</var>.
+
+    <p class=XXX>The definition should include all inline elements except
+    &lt;br&gt;.  As elsewhere, we have trouble with the exact definition because
+    HTML doesn't classify non-conforming elements, but those are common in
+    editing and need to be handled correctly.
+    <!-- IE also includes <br> (at least for the purposes of the indent
+    command), but this is unlikely to match user expectations. -->
+
+    <li>Otherwise, break from this loop.
+  </ol>
+
+  <li>Repeat the following steps:
+
+  <ol>
+    <li>If <var title="">end offset</var> is 0, set <var title="">end 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="">end node</var> and then set <var title="">end node</var> to its
+    <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+    <li>Otherwise, if <var title="">end node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node or
+    <var title="">end offset</var> 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 <var title="">end
+    node</var>, 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> and then set <var title="">end node</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+    <li>Otherwise, if the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">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> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node, or an (insert
+    definition here), add one to <var title="">end offset</var>.
+
+    <p class=XXX>Same definition as before.
+
+    <li>Otherwise, break from this loop.
+  </ol>
+
+  <li>Let <var title="">new range</var> be a new <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> whose <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> <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> are <var title="">start node</var>,
+  <var title="">start offset</var>, <var title="">end node</var>, and <var title="">end offset</var>.
+
+  <li>Return <var title="">new range</var>.
+</ol>
+
+
+<h2 id="clearing-an-element's-value"><span class=secno>6 </span>Clearing an element's value</h2>
 <p>When a user agent is to <dfn id=clear-the-value>clear the value</dfn> of an element
 <var title="">element</var>, it must run the following steps:
 
@@ -624,7 +695,7 @@
 </ol>
 
 
-<h2 id=pushing-down-values><span class=secno>6 </span>Pushing down values</h2>
+<h2 id=pushing-down-values><span class=secno>7 </span>Pushing down values</h2>
 <p>When a user agent is to <dfn id=push-down-values>push down values</dfn> to a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>
 <var title="">node</var>, given a command <var title="">command</var> and a new value
 <var title="">new value</var>, it must run the following steps:
@@ -741,7 +812,7 @@
 </ol>
 
 
-<h2 id=forcing-the-value-of-a-node><span class=secno>7 </span>Forcing the value of a node</h2>
+<h2 id=forcing-the-value-of-a-node><span class=secno>8 </span>Forcing the value of a node</h2>
 <p>When a user agent is to <dfn id=force-the-value>force the value</dfn> of a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>
 <var title="">node</var>, given a command <var title="">command</var> and a new value
 <var title="">new value</var>, it must run the following steps.
@@ -1051,7 +1122,7 @@
 </ol>
 
 
-<h2 id=setting-the-value-of-a-node><span class=secno>8 </span>Setting the value of a node</h2>
+<h2 id=setting-the-value-of-a-node><span class=secno>9 </span>Setting the value of a node</h2>
 <p>When a user agent is to <dfn id=set-the-value>set the value</dfn> of a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>
 <var title="">node</var>, it must run the following steps.  There are two inputs: a
 command <var title="">command</var> and a new value <var title="">new value</var>.
@@ -1205,7 +1276,7 @@
 </ol>
 
 
-<h2 id=commands><span class=secno>9 </span>Commands</h2>
+<h2 id=commands><span class=secno>10 </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
 <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> interface allows scripts to
@@ -1727,82 +1798,42 @@
 
 <dd><strong>Action</strong>:
 
+<p class=XXX>Handle corner cases: endpoints are detached, documents, document
+fragments, html/body, head or things in head . . .
+
 <ol>
-  <li>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 the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>.
-
-  <p class=XXX>Handle corner cases: endpoints are detached, documents, document
-  fragments, html/body, head or things in head . . .
-
-  <li>Repeat the following steps:
-
-  <ol>
-    <li>If <var title="">start node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node or
-    <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> and then set <var title="">start node</var> to its
-    <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
-
-    <li>Otherwise, if <var title="">start offset</var> 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 <var title="">start node</var>, set <var title="">start 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="">start node</var> and then set <var title="">start
-    node</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
-
-    <li>Otherwise, if the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">start node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
-    <var title="">start offset</var> minus one is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node, or an
-    (insert definition here), subtract one from <var title="">start offset</var>.
-
-    <p class=XXX>The definition should include all inline elements except
-    &lt;br&gt;.  As elsewhere, we have trouble with the exact definition because
-    HTML doesn't classify non-conforming elements, but those are common in
-    editing and need to be handled correctly.
-
-    <li>Otherwise, break from this loop.
-  </ol>
-
-  <li>Repeat the following steps:
+  <li><a href=#block-extend>Block-extend</a> the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>, and let <var title="">new range</var> be
+  the result.
+
+  <!-- If the first line box in a block box starts with a non-preserved line
+  break, the line break will create an empty line box.  If the line line box in
+  a block box ends with a non-preserved line break, however, it will not create
+  an extra line box.  This means that if you have foo<br>bar, there will be no
+  empty line in between, but if you change it to <div>foo</div><br>bar, you've
+  created an extra line.  Chrome 12 dev thus deletes a <br> that immediately
+  follows a <blockquote> it creates.  Firefox 4.0 and Opera 11.00 instead just
+  includes it in the blockquote, so it has no effect.  The issue doesn't come
+  up for IE9, since it indents the whole block and doesn't treat <br>
+  differently from other inline elements.  I follow WebKit because it produces
+  slightly neater markup. -->
+  <li>If the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">new 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> with
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> equal to its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>:
 
   <ol>
-    <li>If <var title="">end offset</var> is 0, set <var title="">end 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="">end node</var> and then set <var title="">end node</var> to its
-    <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
-
-    <li>Otherwise, if <var title="">end node</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node or
-    <var title="">end offset</var> 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 <var title="">end
-    node</var>, 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> and then set <var title="">end node</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
-
-    <li>Otherwise, if the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">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> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node, or an (insert
-    definition here), add one to <var title="">end offset</var>.
-
-    <p class=XXX>Same definition as before.
-
-    <li>Otherwise, if the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">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> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, remove it from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> and break
-    from this loop.
-    <!-- If the first line box in a block box starts with a non-preserved line
-    break, the line break will create an empty line box.  If the line line box
-    in a block box ends with a non-preserved line break, however, it will not
-    create an extra line box.  This means that if you have foo<br>bar, there
-    will be no empty line in between, but if you change it to
-    <div>foo</div><br>bar, you've created an extra line.  Chrome 12 dev thus
-    deletes a <br> that immediately follows a <blockquote> it creates.  Firefox
-    4.0 and Opera 11.00 instead just includes it in the blockquote, so it has
-    no effect.  The issue doesn't come up for IE9, since it indents the whole
-    block and doesn't treat <br> differently from other inline elements.  I
-    follow WebKit because it produces slightly neater markup. -->
-
-    <li>Otherwise, break from this loop.
+    <li>Remove that <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+    <li>While 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-offset title=concept-boundary-point-offset>offset</a> of <var title="">new range</var> 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-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>, set 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> of
+    <var title="">new range</var> to (<a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of <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>, 1 +
+    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <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>Let <var title="">new range</var> be a new <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> whose <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and
-  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> <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> are <var title="">start node</var>,
-  <var title="">start offset</var>, <var title="">end node</var>, and <var title="">end offset</var>.
-
-  <li>Let <var title="">node list</var> be all <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new
-  range</var>, omitting any that cannot be the child of a <code class=external data-anolis-spec=html title="the blockquote element"><a href=http://www.whatwg.org/html/#the-blockquote-element>blockquote</a></code> and
-  omitting any with 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> already in <var title="">node list</var>.
+  <li>Let <var title="">node list</var> be a list of <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>, initially empty.
+
+  <li>For each <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> <var title="">node</var> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new range</var>,
+  if <var title="">node</var> can be the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of a <code class=external data-anolis-spec=html title="the blockquote element"><a href=http://www.whatwg.org/html/#the-blockquote-element>blockquote</a></code> and if no
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of <var title="">node</var> is in <var title="">node list</var>, append
+  <var title="">node</var> to <var title="">node list</var>.
 
   <li>For each <var title="">node</var> in <var title="">node list</var>:
 
@@ -1837,7 +1868,17 @@
     that requires care to get right in mixed-direction cases.  Even once
     browsers start to support margin-start and so on, we can't use them because
     a) we have to work okay in legacy browsers and b) it doesn't help if a
-    nested block has different direction (so should be indented the other way).
+    descendant block has different direction (so should be indented the other
+    way).
+
+    <p class=XXX>IE9 doesn't handle an explicit margin attribute very well when
+    outdenting: it propagates it to the parent when removing the element, which
+    doesn't actually remove the indentation.  So indentation per spec (or
+    Gecko in CSS mode or WebKit) will not be removed correctly by IE.  I'm
+    leaving the style in because this short-term incompatibility is preferable
+    to the long-term incorrectness of adding top/bottom margins or adding
+    margins on both sides.  I can't think of any better way to do this at the
+    moment.
 
     <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>,
     <a href=#preserving-ranges>preserving ranges</a>.
@@ -1979,6 +2020,107 @@
 <dd><strong>Relevant CSS Property</strong>: "font-style"
 
 
+<dt><code title=""><dfn id=command-outdent title=command-outdent>outdent</dfn></code>
+
+<dd><strong>Action</strong>:
+
+<ol>
+  <li><a href=#block-extend>Block-extend</a> the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>, and let <var title="">new range</var> be
+  the result.
+
+  <li>Let <var title="">node list</var> be all <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new
+  range</var>, 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="">new
+  range</var>.
+
+  <li>For each <var title="">node</var> in <var title="">node list</var>:
+
+  <!--
+  We need to remove all of the following:
+
+  * Plain <blockquote> (produced by Opera 11.00 and non-CSS Firefox 4.0)
+  * <blockquote style="margin-right: 0" dir="ltr"> and <blockquote
+    style="margin-left: 0" dir="rtl"> (IE9)
+  * <blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px;
+    border: none; padding: 0px"> (Chrome 12 dev)
+  * <div style="margin-left: 40px"> and <div style="margin-right: 40px">
+    (CSS Firefox 4.0 if no other element available)
+  * <blockquote style="margin: 0 40px"> and <div style="margin: 0 40px"> (spec)
+  * Other random things with display: block whose left or right margin was
+    increased by 40px (CSS Firefox 4.0)
+  -->
+  <ol>
+    <!-- The easy case is when the whole element is indented.  In this case we
+    remove the whole thing indiscriminately.  In the case of blockquotes
+    created by IE, this might change the direction of some children, but then
+    their direction was probably changed incorrectly in the first place, so no
+    harm. -->
+    <li>If <var title="">node</var> is a <code class=external data-anolis-spec=html title="the div element"><a href=http://www.whatwg.org/html/#the-div-element>div</a></code> or <code class=external data-anolis-spec=html title="the blockquote element"><a href=http://www.whatwg.org/html/#the-blockquote-element>blockquote</a></code>, and it has no
+    attributes other than one or more of
+
+    <ol type=a>
+      <li>a <code class=external data-anolis-spec=html title="the style attribute"><a href=http://www.whatwg.org/html/#the-style-attribute>style</a></code> attribute that sets no properties other than "margin",
+      "border", "padding", or subproperties of those;
+
+      <li>a <code class=external data-anolis-spec=html title=classes><a href=http://www.whatwg.org/html/#classes>class</a></code> attribute
+      that sets exactly one class;
+
+      <li>a <code class=external data-anolis-spec=html title="the dir attribute"><a href=http://www.whatwg.org/html/#the-dir-attribute>dir</a></code>
+      attribute;
+    </ol>
+
+    then:
+
+    <ol>
+      <li>If <var title="">node</var>'s 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> and <code class=external data-anolis-spec=domcore title=dom-Node-nextSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling>nextSibling</a></code> are both
+      <a href=#inline-node title="inline node">inline nodes</a> or its first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> and
+      <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> are both <a href=#inline-node title="inline node">inline
+      nodes</a>:
+
+      <ol>
+        <li>Let <var title="">new parent</var> be the result of calling <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("div")</a></code> on the
+        <code class=external data-anolis-spec=domcore title=dom-Node-ownerDocument><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-ownerdocument>ownerDocument</a></code> of <var title="">node</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>
+        immediately before <var title="">node</var>.
+
+        <li>While <var title="">node</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, append its first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>
+        as <var title="">new parent</var>'s last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>, <a href=#preserving-ranges>preserving
+        ranges</a>.
+      </ol>
+
+      <li>Otherwise, while <var title="">node</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, insert its first
+      <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> into its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> immediately before it, <a href=#preserving-ranges>preserving
+      ranges</a>.
+
+      <li>Remove <var title="">node</var> from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+      <li>Continue with the next <var title="">node</var>.
+    </ol>
+  </ol>
+
+  <!-- No browser handles the case of Firefox 4.0 in CSS mode, where it adds a
+  margin attribute to an existing element, including Firefox itself.  So let's
+  just skip it. -->
+
+  <li class=XXX>If it's a blockquote element or an indented div that doesn't
+  otherwise meet our criteria, then . . .
+
+  <li class=XXX>If some ancestor is indenting us, then . . .
+
+  <!-- No indentation to remove from this node, but maybe some descendant has.
+  We only want to remove one level of indentation, so we only run this step if
+  we didn't remove indentation already. -->
+  <li>If <var title="">node</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, insert all of its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a> into
+  <var title="">node list</var> immediately after <var title="">node</var>, so that the next
+  <var title="">node</var> to be processed is the first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of the current
+  <var title="">node</var>.
+</ol>
+
+<dd><strong>State</strong>:
+
+<dd><strong>Value</strong>:
+
+
 <dt><code title=""><dfn id=command-removeformat title=command-removeformat>removeFormat</dfn></code>
 <!--
 Tested in IE 9, Firefox 4.0, Chrome 12 dev, Opera 11.00.
--- a/implementation.js	Thu Apr 28 12:54:21 2011 -0600
+++ b/implementation.js	Thu Apr 28 15:01:23 2011 -0600
@@ -327,6 +327,15 @@
 		&& isHtmlNamespace(node.namespaceURI);
 }
 
+// "An inline node is either a Text node, or an Element whose "display"
+// property computes to "inline", "inline-block", or "inline-table"."
+function isInlineNode(node) {
+	return node
+		&& (node.nodeType == Node.TEXT_NODE
+		|| (node.nodeType == Node.ELEMENT_NODE
+		&& ["inline", "inline-block", "inline-table"].indexOf(getComputedStyle(node).display) != -1));
+}
+
 // "Something is editable if either it is an Element with a contenteditable
 // attribute set to the true state; or it is a Document whose designMode is
 // enabled; or it is a node whose parent is editable, but which does not have a
@@ -1034,6 +1043,87 @@
 	return ret;
 }
 
+function blockExtendRange(range) {
+	// "Let start node, start offset, end node, and end offset be the start
+	// and end nodes and offsets of the range."
+	var startNode = range.startContainer;
+	var startOffset = range.startOffset;
+	var endNode = range.endContainer;
+	var endOffset = range.endOffset;
+
+	// "Repeat the following steps:"
+	while (true) {
+		// "If start node is a Text or Comment node or start offset is 0,
+		// set start offset to the index of start node and then set start
+		// node to its parent."
+		if (startNode.nodeType == Node.TEXT_NODE
+		|| startNode.nodeType == Node.COMMENT_NODE
+		|| startOffset == 0) {
+			startOffset = getNodeIndex(startNode);
+			startNode = startNode.parentNode;
+
+		// "Otherwise, if start offset is equal to the length of start
+		// node, set start offset to one plus the index of start node and
+		// then set start node to its parent."
+		} else if (startOffset == getNodeLength(startNode)) {
+			startOffset = 1 + getNodeIndex(startNode);
+			startNode = startNode.parentNode;
+
+		// "Otherwise, if the child of start node with index start offset
+		// minus one is a Text or Comment node, or an (insert definition
+		// here), subtract one from start offset."
+		} else if (startNode.childNodes[startOffset - 1].nodeType == Node.TEXT_NODE
+		|| startNode.childNodes[startOffset - 1].nodeType == Node.COMMENT_NODE
+		|| ["B", "I", "SPAN"].indexOf(startNode.childNodes[startOffset - 1].tagName) != -1) {
+			startOffset--;
+
+		// "Otherwise, break from this loop."
+		} else {
+			break;
+		}
+	}
+
+	// "Repeat the following steps:"
+	while (true) {
+		// "If end offset is 0, set end offset to the index of end node and
+		// then set end node to its parent."
+		if (endOffset == 0) {
+			endOffset = getNodeIndex(endNode);
+			endNode = endNode.parentNode;
+
+		// "Otherwise, if end node is a Text or Comment node or end offset
+		// is equal to the length of end node, set end offset to one plus
+		// the index of end node and then set end node to its parent."
+		} else if (endNode.nodeType == Node.TEXT_NODE
+		|| endNode.nodeType == Node.COMMENT_NODE
+		|| endOffset == getNodeLength(endNode)) {
+			endOffset = 1 + getNodeIndex(endNode);
+			endNode = endNode.parentNode;
+
+		// "Otherwise, if the child of end node with index end offset is a
+		// Text or Comment node, or an (insert definition here), add one to
+		// end offset."
+		} else if (endNode.childNodes[endOffset].nodeType == Node.TEXT_NODE
+		|| endNode.childNodes[endOffset].nodeType == Node.COMMENT_NODE
+		|| ["B", "I", "SPAN"].indexOf(endNode.childNodes[endOffset].tagName) != -1) {
+			endOffset++;
+
+		// "Otherwise, break from this loop."
+		} else {
+			break;
+		}
+	}
+
+	// "Let new range be a new range whose start and end nodes and offsets
+	// are start node, start offset, end node, and end offset."
+	var newRange = startNode.ownerDocument.createRange();
+	newRange.setStart(startNode, startOffset);
+	newRange.setEnd(endNode, endOffset);
+
+	// "Return new range."
+	return newRange;
+}
+
 function clearValue(element, command) {
 	// "If element's specified value for command is null, return the empty
 	// list."
@@ -1939,93 +2029,30 @@
 		break;
 
 		case "indent":
-		// "Let start node, start offset, end node, and end offset be the start
-		// and end nodes and offsets of the range."
-		var startNode = range.startContainer;
-		var startOffset = range.startOffset;
-		var endNode = range.endContainer;
-		var endOffset = range.endOffset;
-
-		// "Repeat the following steps:"
-		while (true) {
-			// "If start node is a Text or Comment node or start offset is 0,
-			// set start offset to the index of start node and then set start
-			// node to its parent."
-			if (startNode.nodeType == Node.TEXT_NODE
-			|| startNode.nodeType == Node.COMMENT_NODE
-			|| startOffset == 0) {
-				startOffset = getNodeIndex(startNode);
-				startNode = startNode.parentNode;
+		// "Block-extend the range, and let new range be the result."
+		var newRange = blockExtendRange(range);
 
-			// "Otherwise, if start offset is equal to the length of start
-			// node, set start offset to one plus the index of start node and
-			// then set start node to its parent."
-			} else if (startOffset == getNodeLength(startNode)) {
-				startOffset = 1 + getNodeIndex(startNode);
-				startNode = startNode.parentNode;
+		// "If the child of new range's end node with index equal to its end
+		// offset is a br:"
+		var end = newRange.endContainer.childNodes[newRange.endOffset];
+		if (isHtmlElement(end) && end.tagName == "BR") {
+			// "Remove that br from its parent."
+			end.parentNode.removeChild(br);
 
-			// "Otherwise, if the child of start node with index start offset
-			// minus one is a Text or Comment node, or an (insert definition
-			// here), subtract one from start offset."
-			} else if (startNode.childNodes[startOffset - 1].nodeType == Node.TEXT_NODE
-			|| startNode.childNodes[startOffset - 1].nodeType == Node.COMMENT_NODE
-			|| ["B", "I", "SPAN"].indexOf(startNode.childNodes[startOffset - 1].tagName) != -1) {
-				startOffset--;
-
-			// "Otherwise, break from this loop."
-			} else {
-				break;
+			// "While the end offset of new range is equal to the length of its
+			// end node, set the end of new range to (parent of end node, 1 +
+			// index of end node)."
+			while (newRange.endOffset == getNodeLength(newRange.endContainer)) {
+				newRange.setEnd(newRange.endContainer.parentNode, 1 + getNodeIndex(newRange.endContainer));
 			}
 		}
 
-		// "Repeat the following steps:"
-		while (true) {
-			// "If end offset is 0, set end offset to the index of end node and
-			// then set end node to its parent."
-			if (endOffset == 0) {
-				endOffset = getNodeIndex(endNode);
-				endNode = endNode.parentNode;
-
-			// "Otherwise, if end node is a Text or Comment node or end offset
-			// is equal to the length of end node, set end offset to one plus
-			// the index of end node and then set end node to its parent."
-			} else if (endNode.nodeType == Node.TEXT_NODE
-			|| endNode.nodeType == Node.COMMENT_NODE
-			|| endOffset == getNodeLength(endNode)) {
-				endOffset = 1 + getNodeIndex(endNode);
-				endNode = endNode.parentNode;
+		// "Let node list be a list of nodes, initially empty."
+		var nodeList = [];
 
-			// "Otherwise, if the child of end node with index end offset is a
-			// Text or Comment node, or an (insert definition here), add one to
-			// end offset."
-			} else if (endNode.childNodes[endOffset].nodeType == Node.TEXT_NODE
-			|| endNode.childNodes[endOffset].nodeType == Node.COMMENT_NODE
-			|| ["B", "I", "SPAN"].indexOf(endNode.childNodes[endOffset].tagName) != -1) {
-				endOffset++;
-
-			// "Otherwise, if the child of end node with index end offset is a br,
-			// remove it from its parent and break from this loop."
-			} else if (isHtmlElement(endNode.childNodes[endOffset])
-			&& endNode.childNodes[endOffset].tagName == "BR") {
-				endNode.removeChild(endNode.childNodes[endOffset]);
-				break;
-
-			// "Otherwise, break from this loop."
-			} else {
-				break;
-			}
-		}
-
-		// "Let new range be a new range whose start and end nodes and offsets
-		// are start node, start offset, end node, and end offset."
-		var newRange = startNode.ownerDocument.createRange();
-		newRange.setStart(startNode, startOffset);
-		newRange.setEnd(endNode, endOffset);
-
-		// "Let node list be all nodes contained in new range, omitting any
-		// that cannot be the child of a blockquote and omitting any with an
-		// ancestor already in node list."
-		var nodeList = [];
+		// "For each node node contained in new range, if node can be the child
+		// of a blockquote and if no ancestor of node is in node list, append
+		// node to node list."
 		for (var node = newRange.startContainer; node != nextNodeDescendants(newRange.endContainer); node = nextNode(node)) {
 			if (!isContained(node, newRange)) {
 				continue;
@@ -2036,6 +2063,8 @@
 				continue;
 			}
 
+			// We only need to check that the last member isn't an ancestor,
+			// because no ancestor of a member can be in the list.
 			if (nodeList.length
 			&& isAncestor(nodeList[nodeList.length - 1], node)) {
 				continue;
@@ -2084,7 +2113,6 @@
 			// ranges."
 			movePreservingRanges(node, newParent, 0);
 		}
-
 		break;
 
 		case "inserthorizontalrule":
@@ -2239,6 +2267,90 @@
 		}
 		break;
 
+		case "outdent":
+		// "Block-extend the range, and let new range be the result."
+		var newRange = blockExtendRange(range);
+
+		// "Let node list be all nodes contained in new range, omitting any
+		// whose parent is also contained in new range."
+		var nodeList = [];
+		for (
+			var node = newRange.startContainer;
+			node != nextNodeDescendants(newRange.endContainer);
+			node = nextNode(node)
+		) {
+			if (isContained(node, newRange)
+			&& !isContained(node.parentNode, newRange)) {
+				nodeList.push(node);
+			}
+		}
+
+		// "For each node in node list:"
+		for (var i = 0; i < nodeList.length; i++) {
+			var node = nodeList[i];
+
+			// "If node is a div or blockquote, and it has no attributes other
+			// than one or more of
+			//   "a. a style attribute that sets no properties other than
+			//       "margin", "border", "padding", or subproperties of those;
+			//   "b. a class attribute that sets exactly one class;
+			//   "c. a dir attribute;
+			// "then:"
+			//
+			// Not going to implement all the fancy checks, too much effort for
+			// a test implementation.
+			if (isHtmlElement(node)
+			&& (node.tagName == "BLOCKQUOTE"
+			|| (
+				node.tagName == "DIV"
+				&& node.attributes.length == 1
+				&& ["margin-left: 40px", "margin-right: 40px", "margin: 0 40px"].indexOf(node.getAttribute("style")) != -1
+			))) {
+				// "If node's last child and nextSibling are both inline nodes
+				// or its first child and previousSibling are both inline
+				// nodes:"
+				if ((isInlineNode(node.lastChild) && isInlineNode(node.nextSibling))
+				|| (isInlineNode(node.firstChild) && isInlineNode(node.previousSibling))) {
+					// "Let new parent be the result of calling
+					// createElement("div") on the ownerDocument of node."
+					var newParent = node.ownerDocument.createElement("div");
+
+					// "Insert new parent into node's parent immediately before
+					// node."
+					node.parentNode.insertBefore(newParent, node);
+
+					// "While node has children, append its first child as new
+					// parent's last child, preserving ranges."
+					while (node.firstChild) {
+						movePreservingRanges(node.firstChild, newParent, newParent.childNodes.length);
+					}
+
+				// "Otherwise, while node has children, insert its first child
+				// into its parent immediately before it, preserving ranges."
+				} else {
+					while (node.firstChild) {
+						movePreservingRanges(node.firstChild, node.parentNode, getNodeIndex(node));
+					}
+				}
+
+				// "Remove node from its parent."
+				node.parentNode.removeChild(node);
+
+				// "Continue with the next node."
+				continue;
+			}
+
+			// "If node has children, insert all of its children into node list
+			// immediately after node, so that the next node to be processed is
+			// the first child of the current node."
+			if (node.firstChild) {
+				nodeList = nodeList.slice(0, i + 1)
+					.concat(Array.prototype.slice.call(node.childNodes))
+					.concat(nodeList.slice(i + 1));
+			}
+		}
+		break;
+
 		case "removeformat":
 		// "Decompose the range, and let node list be the result."
 		var nodeList = decomposeRange(range);
--- a/preprocess	Thu Apr 28 12:54:21 2011 -0600
+++ b/preprocess	Thu Apr 28 15:01:23 2011 -0600
@@ -24,6 +24,8 @@
     '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>',
+    'directionality': '<span data-anolis-spec=html title="the directionality">directionality</span>',
+    'div': '<code data-anolis-spec=html title="the div element">div</code>',
     'document': '<code data-anolis-spec=domcore>Document</code>',
     'documentfragment': '<code data-anolis-spec=domcore>DocumentFragment</code>',
     'element': '<code data-anolis-spec=domcore>Element</code>',
--- a/source.html	Thu Apr 28 12:54:21 2011 -0600
+++ b/source.html	Thu Apr 28 15:01:23 2011 -0600
@@ -169,6 +169,9 @@
 <p>An <dfn>HTML element</dfn> is an [[element]] whose [[namespace]] is the
 [[htmlnamespace]].
 
+<p>An <dfn>inline node</dfn> is either a [[text]] node, or an [[element]] whose
+"display" property computes to "inline", "inline-block", or "inline-table".
+
 <p>Something is <dfn>editable</dfn> if either it is an [[element]] with a <code
 data-anolis-spec=html title=attr-contenteditable>contenteditable</code>
 attribute set to the true state; or it is a [[document]] whose <code
@@ -529,6 +532,73 @@
 </ol>
 
 
+<h2>Block-extending a range</h2>
+<p>When a user agent is to <dfn>block-extend</dfn> a [[range]]
+<var>range</var>, it must run the following steps:
+
+<p class=XXX>Surely I can come up with a better name.
+
+<ol>
+  <li>Let <var>start node</var>, <var>start offset</var>, <var>end node</var>,
+  and <var>end offset</var> be the [[rangestart]] and [[rangeend]] [[bpnodes]]
+  and [[bpoffsets]] of <var>range</var>.
+
+  <li>Repeat the following steps:
+
+  <ol>
+    <li>If <var>start node</var> is a [[text]] or [[comment]] node or
+    <var>start offset</var> is 0, set <var>start offset</var> to the [[index]]
+    of <var>start node</var> and then set <var>start node</var> to its
+    [[parent]].
+
+    <li>Otherwise, if <var>start offset</var> is equal to the
+    [[nodelength]] of <var>start node</var>, set <var>start offset</var> to one
+    plus the [[index]] of <var>start node</var> and then set <var>start
+    node</var> to its [[parent]].
+
+    <li>Otherwise, if the [[child]] of <var>start node</var> with [[index]]
+    <var>start offset</var> minus one is a [[text]] or [[comment]] node, or an
+    (insert definition here), subtract one from <var>start offset</var>.
+
+    <p class=XXX>The definition should include all inline elements except
+    &lt;br>.  As elsewhere, we have trouble with the exact definition because
+    HTML doesn't classify non-conforming elements, but those are common in
+    editing and need to be handled correctly.
+    <!-- IE also includes <br> (at least for the purposes of the indent
+    command), but this is unlikely to match user expectations. -->
+
+    <li>Otherwise, break from this loop.
+  </ol>
+
+  <li>Repeat the following steps:
+
+  <ol>
+    <li>If <var>end offset</var> is 0, set <var>end offset</var> to the
+    [[index]] of <var>end node</var> and then set <var>end node</var> to its
+    [[parent]].
+
+    <li>Otherwise, if <var>end node</var> is a [[text]] or [[comment]] node or
+    <var>end offset</var> is equal to the [[nodelength]] of <var>end
+    node</var>, set <var>end offset</var> to one plus the [[index]] of <var>end
+    node</var> and then set <var>end node</var> to its [[parent]].
+
+    <li>Otherwise, if the [[child]] of <var>end node</var> with [[index]]
+    <var>end offset</var> is a [[text]] or [[comment]] node, or an (insert
+    definition here), add one to <var>end offset</var>.
+
+    <p class=XXX>Same definition as before.
+
+    <li>Otherwise, break from this loop.
+  </ol>
+
+  <li>Let <var>new range</var> be a new [[range]] whose [[rangestart]] and
+  [[rangeend]] [[bpnodes]] and [[bpoffsets]] are <var>start node</var>,
+  <var>start offset</var>, <var>end node</var>, and <var>end offset</var>.
+
+  <li>Return <var>new range</var>.
+</ol>
+
+
 <h2>Clearing an element's value</h2>
 <p>When a user agent is to <dfn>clear the value</dfn> of an element
 <var>element</var>, it must run the following steps:
@@ -1743,82 +1813,42 @@
 
 <dd><strong>Action</strong>:
 
+<p class=XXX>Handle corner cases: endpoints are detached, documents, document
+fragments, html/body, head or things in head . . .
+
 <ol>
-  <li>Let <var>start node</var>, <var>start offset</var>, <var>end node</var>,
-  and <var>end offset</var> be the [[rangestart]] and [[rangeend]] [[bpnodes]]
-  and [[bpoffsets]] of the [[range]].
-
-  <p class=XXX>Handle corner cases: endpoints are detached, documents, document
-  fragments, html/body, head or things in head . . .
-
-  <li>Repeat the following steps:
-
-  <ol>
-    <li>If <var>start node</var> is a [[text]] or [[comment]] node or
-    <var>start offset</var> is 0, set <var>start offset</var> to the [[index]]
-    of <var>start node</var> and then set <var>start node</var> to its
-    [[parent]].
-
-    <li>Otherwise, if <var>start offset</var> is equal to the
-    [[nodelength]] of <var>start node</var>, set <var>start offset</var> to one
-    plus the [[index]] of <var>start node</var> and then set <var>start
-    node</var> to its [[parent]].
-
-    <li>Otherwise, if the [[child]] of <var>start node</var> with [[index]]
-    <var>start offset</var> minus one is a [[text]] or [[comment]] node, or an
-    (insert definition here), subtract one from <var>start offset</var>.
-
-    <p class=XXX>The definition should include all inline elements except
-    &lt;br>.  As elsewhere, we have trouble with the exact definition because
-    HTML doesn't classify non-conforming elements, but those are common in
-    editing and need to be handled correctly.
-
-    <li>Otherwise, break from this loop.
-  </ol>
-
-  <li>Repeat the following steps:
+  <li><span>Block-extend</span> the [[range]], and let <var>new range</var> be
+  the result.
+
+  <!-- If the first line box in a block box starts with a non-preserved line
+  break, the line break will create an empty line box.  If the line line box in
+  a block box ends with a non-preserved line break, however, it will not create
+  an extra line box.  This means that if you have foo<br>bar, there will be no
+  empty line in between, but if you change it to <div>foo</div><br>bar, you've
+  created an extra line.  Chrome 12 dev thus deletes a <br> that immediately
+  follows a <blockquote> it creates.  Firefox 4.0 and Opera 11.00 instead just
+  includes it in the blockquote, so it has no effect.  The issue doesn't come
+  up for IE9, since it indents the whole block and doesn't treat <br>
+  differently from other inline elements.  I follow WebKit because it produces
+  slightly neater markup. -->
+  <li>If the [[child]] of <var>new range</var>'s [[rangeend]] [[bpnode]] with
+  [[index]] equal to its [[rangeend]] [[bpoffset]] is a [[br]]:
 
   <ol>
-    <li>If <var>end offset</var> is 0, set <var>end offset</var> to the
-    [[index]] of <var>end node</var> and then set <var>end node</var> to its
-    [[parent]].
-
-    <li>Otherwise, if <var>end node</var> is a [[text]] or [[comment]] node or
-    <var>end offset</var> is equal to the [[nodelength]] of <var>end
-    node</var>, set <var>end offset</var> to one plus the [[index]] of <var>end
-    node</var> and then set <var>end node</var> to its [[parent]].
-
-    <li>Otherwise, if the [[child]] of <var>end node</var> with [[index]]
-    <var>end offset</var> is a [[text]] or [[comment]] node, or an (insert
-    definition here), add one to <var>end offset</var>.
-
-    <p class=XXX>Same definition as before.
-
-    <li>Otherwise, if the [[child]] of <var>end node</var> with [[index]]
-    <var>end offset</var> is a [[br]], remove it from its [[parent]] and break
-    from this loop.
-    <!-- If the first line box in a block box starts with a non-preserved line
-    break, the line break will create an empty line box.  If the line line box
-    in a block box ends with a non-preserved line break, however, it will not
-    create an extra line box.  This means that if you have foo<br>bar, there
-    will be no empty line in between, but if you change it to
-    <div>foo</div><br>bar, you've created an extra line.  Chrome 12 dev thus
-    deletes a <br> that immediately follows a <blockquote> it creates.  Firefox
-    4.0 and Opera 11.00 instead just includes it in the blockquote, so it has
-    no effect.  The issue doesn't come up for IE9, since it indents the whole
-    block and doesn't treat <br> differently from other inline elements.  I
-    follow WebKit because it produces slightly neater markup. -->
-
-    <li>Otherwise, break from this loop.
+    <li>Remove that [[br]] from its [[parent]].
+
+    <li>While the [[rangeend]] [[bpoffset]] of <var>new range</var> is equal to
+    the [[nodelength]] of its [[rangeend]] [[bpnode]], set the [[rangeend]] of
+    <var>new range</var> to ([[parent]] of [[rangeend]] [[bpnode]], 1 +
+    [[index]] of [[rangeend]] [[bpnode]]).
   </ol>
 
-  <li>Let <var>new range</var> be a new [[range]] whose [[rangestart]] and
-  [[rangeend]] [[bpnodes]] and [[bpoffsets]] are <var>start node</var>,
-  <var>start offset</var>, <var>end node</var>, and <var>end offset</var>.
-
-  <li>Let <var>node list</var> be all [[nodes]] [[contained]] in <var>new
-  range</var>, omitting any that cannot be the child of a [[blockquote]] and
-  omitting any with an [[ancestor]] already in <var>node list</var>.
+  <li>Let <var>node list</var> be a list of [[nodes]], initially empty.
+
+  <li>For each [[node]] <var>node</var> [[contained]] in <var>new range</var>,
+  if <var>node</var> can be the [[child]] of a [[blockquote]] and if no
+  [[ancestor]] of <var>node</var> is in <var>node list</var>, append
+  <var>node</var> to <var>node list</var>.
 
   <li>For each <var>node</var> in <var>node list</var>:
 
@@ -1855,7 +1885,17 @@
     that requires care to get right in mixed-direction cases.  Even once
     browsers start to support margin-start and so on, we can't use them because
     a) we have to work okay in legacy browsers and b) it doesn't help if a
-    nested block has different direction (so should be indented the other way).
+    descendant block has different direction (so should be indented the other
+    way).
+
+    <p class=XXX>IE9 doesn't handle an explicit margin attribute very well when
+    outdenting: it propagates it to the parent when removing the element, which
+    doesn't actually remove the indentation.  So indentation per spec (or
+    Gecko in CSS mode or WebKit) will not be removed correctly by IE.  I'm
+    leaving the style in because this short-term incompatibility is preferable
+    to the long-term incorrectness of adding top/bottom margins or adding
+    margins on both sides.  I can't think of any better way to do this at the
+    moment.
 
     <li>Append <var>node</var> as the last [[child]] of <var>new parent</var>,
     <span>preserving ranges</span>.
@@ -2016,6 +2056,109 @@
 <dd><strong>Relevant CSS Property</strong>: "font-style"
 
 
+<dt><code title><dfn title=command-outdent>outdent</dfn></code>
+
+<dd><strong>Action</strong>:
+
+<ol>
+  <li><span>Block-extend</span> the [[range]], and let <var>new range</var> be
+  the result.
+
+  <li>Let <var>node list</var> be all [[nodes]] [[contained]] in <var>new
+  range</var>, omitting any whose [[parent]] is also [[contained]] in <var>new
+  range</var>.
+
+  <li>For each <var>node</var> in <var>node list</var>:
+
+  <!--
+  We need to remove all of the following:
+
+  * Plain <blockquote> (produced by Opera 11.00 and non-CSS Firefox 4.0)
+  * <blockquote style="margin-right: 0" dir="ltr"> and <blockquote
+    style="margin-left: 0" dir="rtl"> (IE9)
+  * <blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px;
+    border: none; padding: 0px"> (Chrome 12 dev)
+  * <div style="margin-left: 40px"> and <div style="margin-right: 40px">
+    (CSS Firefox 4.0 if no other element available)
+  * <blockquote style="margin: 0 40px"> and <div style="margin: 0 40px"> (spec)
+  * Other random things with display: block whose left or right margin was
+    increased by 40px (CSS Firefox 4.0)
+  -->
+  <ol>
+    <!-- The easy case is when the whole element is indented.  In this case we
+    remove the whole thing indiscriminately.  In the case of blockquotes
+    created by IE, this might change the direction of some children, but then
+    their direction was probably changed incorrectly in the first place, so no
+    harm. -->
+    <li>If <var>node</var> is a [[div]] or [[blockquote]], and it has no
+    attributes other than one or more of
+
+    <ol type=a>
+      <li>a [[style]] attribute that sets no properties other than "margin",
+      "border", "padding", or subproperties of those;
+
+      <li>a <code data-anolis-spec=html title=classes>class</code> attribute
+      that sets exactly one class;
+
+      <li>a <code data-anolis-spec=html title="the dir attribute">dir</code>
+      attribute;
+    </ol>
+
+    then:
+
+    <ol>
+      <li>If <var>node</var>'s last [[child]] and [[nextsibling]] are both
+      <span title="inline node">inline nodes</span> or its first [[child]] and
+      [[previoussibling]] are both <span title="inline node">inline
+      nodes</span>:
+
+      <ol>
+        <li>Let <var>new parent</var> be the result of calling <code
+        data-anolis-spec=domcore
+        title=dom-Document-createElement>createElement("div")</code> on the
+        [[ownerdocument]] of <var>node</var>.
+
+        <li>Insert <var>new parent</var> into <var>node</var>'s [[parent]]
+        immediately before <var>node</var>.
+
+        <li>While <var>node</var> has [[children]], append its first [[child]]
+        as <var>new parent</var>'s last [[child]], <span>preserving
+        ranges</span>.
+      </ol>
+
+      <li>Otherwise, while <var>node</var> has [[children]], insert its first
+      [[child]] into its [[parent]] immediately before it, <span>preserving
+      ranges</span>.
+
+      <li>Remove <var>node</var> from its [[parent]].
+
+      <li>Continue with the next <var>node</var>.
+    </ol>
+  </ol>
+
+  <!-- No browser handles the case of Firefox 4.0 in CSS mode, where it adds a
+  margin attribute to an existing element, including Firefox itself.  So let's
+  just skip it. -->
+
+  <li class=XXX>If it's a blockquote element or an indented div that doesn't
+  otherwise meet our criteria, then . . .
+
+  <li class=XXX>If some ancestor is indenting us, then . . .
+
+  <!-- No indentation to remove from this node, but maybe some descendant has.
+  We only want to remove one level of indentation, so we only run this step if
+  we didn't remove indentation already. -->
+  <li>If <var>node</var> has [[children]], insert all of its [[children]] into
+  <var>node list</var> immediately after <var>node</var>, so that the next
+  <var>node</var> to be processed is the first [[child]] of the current
+  <var>node</var>.
+</ol>
+
+<dd><strong>State</strong>:
+
+<dd><strong>Value</strong>:
+
+
 <dt><code title><dfn title=command-removeformat>removeFormat</dfn></code>
 <!--
 Tested in IE 9, Firefox 4.0, Chrome 12 dev, Opera 11.00.