Initial working indent specification
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Thu, 28 Apr 2011 12:49:11 -0600
changeset 70 471457773bcd
parent 69 bcd801b33f32
child 71 5acce472cf47
Initial working indent specification
autoimplementation.html
editcommands.html
implementation.js
preprocess
source.html
--- a/autoimplementation.html	Thu Apr 14 10:11:40 2011 -0600
+++ b/autoimplementation.html	Thu Apr 28 12:49:11 2011 -0600
@@ -15,18 +15,39 @@
 .line-through { text-decoration: line-through }
 .underline-and-line-through { text-decoration: underline line-through }
 #purple { color: purple }
-td > div:first-child {
+body > div > table > tbody > tr > td > div:first-child {
 	padding-bottom: 0.2em;
 }
-td > div:last-child {
+body > div > table > tbody > tr > td > div:last-child {
 	padding-top: 0.2em;
 	border-top: 1px solid black;
 }
 /* https://bugs.webkit.org/show_bug.cgi?id=56670 */
 dfn { font-style: italic }
+/* Opera has weird default blockquote style */
+blockquote { margin: 1em 40px }
+/* Let the rendered HTML line up so it's easier to compare whitespace */
+body > div > table > tbody > tr > td { vertical-align: top }
 /* We don't want test cells to not wrap */
 listing, plaintext, pre, xmp { white-space: pre-wrap }
 img, video { width: 50px }
+body > div > table {
+	width: 100%;
+	table-layout: fixed;
+}
+body > div > table > tbody > tr > td,
+body > div > table > tbody > tr > th {
+	width: 30%;
+}
+body > div > table > tbody > tr > td:last-child,
+body > div > table > tbody > tr > th:last-child {
+	width: 10%;
+}
+body > div > table > tbody > tr > td:last-child {
+	text-align: center;
+	vertical-align: middle;
+	font-size: 3em;
+}
 </style>
 <p>Legend: {[ are the selection anchor, }] are the selection focus, {}
 represent an element boundary point, [] represent a text node boundary point.
@@ -442,6 +463,9 @@
 		'<span>foo[</span><span>]bar</span><p>extra',
 		'foo[bar]baz<p>extra',
 		'<p dir=rtl>פו[בר]בז<p dir=rtl>נוםף',
+		'<p dir=rtl>פו[ברבז<p>Foobar]baz<p>Extra',
+		'<p>Foo[barbaz<p dir=rtl>פובר]בז<p>Extra',
+		'<div><p>Foo[barbaz<p dir=rtl>פובר]בז</div><p>Extra',
 		'foo]bar[baz<p>extra',
 		'{<p><p> <p>foo</p>}<p>extra',
 		'foo[bar<i>baz]qoz</i>quz<p>extra',
@@ -456,6 +480,9 @@
 		'<p>foo[bar]</p><p>baz</p><p>extra',
 		'<p>[foobar</p><p>ba]z</p><p>extra',
 		'foo[bar]<br>baz<p>extra',
+		'foo[bar]<br><br><br><br>baz<p>extra',
+		'foobar<br>[ba]z<p>extra',
+		'foobar<br><br><br><br>[ba]z<p>extra',
 		'foo[bar<br>ba]z<p>extra',
 
 		// These mimic existing indentation in various browsers, to see how
@@ -1059,7 +1086,7 @@
 	var table = document.querySelectorAll("#" + command + " > table")[idx];
 
 	var tr = document.createElement("tr");
-	table.appendChild(tr);
+	table.firstChild.appendChild(tr);
 
 	return tr;
 }
@@ -1117,6 +1144,9 @@
 		specCell.lastChild.style.color = "red";
 		specCell.lastChild.style.fontWeight = "bold";
 		specCell.lastChild.textContent = "Note, exception: " + e;
+		if ("stack" in e) {
+			specCell.lastChild.textContent += " (stack: " + e.stack + ")";
+		}
 	}
 
 	var key = "execcommand-" + command
@@ -1127,10 +1157,10 @@
 	localStorage[key] = specCell.lastChild.textContent;
 
 	if (oldValue !== null && oldValue !== undefined && oldValue != specCell.lastChild.textContent) {
-		specCell.appendChild(document.createElement("div"));
-		specCell.lastChild.style.color = "red";
-		specCell.lastChild.style.fontWeight = "bold";
-		specCell.lastChild.textContent = "Note, last run produced different markup: " + oldValue;
+		specCell.lastChild.appendChild(document.createElement("div"));
+		specCell.lastChild.lastChild.style.color = "red";
+		specCell.lastChild.lastChild.style.fontWeight = "bold";
+		specCell.lastChild.lastChild.textContent = "Note, last run produced different markup: " + oldValue;
 	}
 }
 
@@ -1189,6 +1219,9 @@
 		browserCell.lastChild.style.color = "red";
 		browserCell.lastChild.style.fontWeight = "bold";
 		browserCell.lastChild.textContent = "Exception: " + e;
+		if ("stack" in e) {
+			specCell.lastChild.textContent += " (stack: " + e.stack + ")";
+		}
 		if (testDiv && testDiv.parentNode != browserCell) {
 			browserCell.insertBefore(testDiv, browserCell.firstChild);
 		}
@@ -1201,16 +1234,21 @@
 	try {
 		// Ad hoc normalization to avoid basically spurious mismatches.  For
 		// now this includes ignoring where the selection goes.
-		var normalizedSpecCell = tr.childNodes[1].childNodes[1].textContent
+		var normalizedSpecCell = tr.childNodes[1].lastChild.firstChild.textContent
 			.replace(/[[\]{}]/g, "")
+			.replace(/ style="margin: 0 40px"/g, "")
 			.replace(/: /g, ":")
 			.replace(/;? ?"/g, '"')
 			.replace(/<(\/?)strong/g, '<$1b')
 			.replace(/<(\/?)strike/g, '<$1s')
 			.replace(/<(\/?)em/g, '<$1i')
 			.replace(/#[0-9a-fA-F]{6}/g, function(match) { return match.toUpperCase(); });
-		var normalizedBrowserCell = tr.childNodes[2].childNodes[1].textContent
+		var normalizedBrowserCell = tr.childNodes[2].lastChild.firstChild.textContent
 			.replace(/[[\]{}]/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')
@@ -1240,16 +1278,14 @@
 	tr.appendChild(sameCell);
 
 	// Insert zero-width spaces so IE doesn't stretch the screen
-	try {
-		tr.childNodes[2].childNodes[1].textContent =
-			tr.childNodes[2].childNodes[1].textContent
-			.replace(/</g, "\u200B<")
-			.replace(/>/g, ">\u200B");
-		tr.childNodes[1].childNodes[1].textContent =
-			tr.childNodes[1].childNodes[1].textContent
-			.replace(/</g, "\u200B<")
-			.replace(/>/g, ">\u200B");
-	} catch (e) {};
+	for (var i = 0; i <= 2; i++) {
+		try {
+			tr.childNodes[i].lastChild.firstChild.textContent =
+				tr.childNodes[i].lastChild.firstChild.textContent
+				.replace(/</g, "\u200B<")
+				.replace(/>/g, ">\u200B");
+		} catch (e) {};
+	}
 }
 
 function doTearDown(command) {
--- a/editcommands.html	Thu Apr 14 10:11:40 2011 -0600
+++ b/editcommands.html	Thu Apr 28 12:49:11 2011 -0600
@@ -27,7 +27,7 @@
 <body class=draft>
 <div class=head id=head>
 <h1>HTML Editing Commands</h1>
-<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-14-april-2011>Work in Progress &mdash; Last Update 14 April 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-28-april-2011>Work in Progress &mdash; Last Update 28 April 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;ayg+spec@aryeh.name&gt;
@@ -1681,21 +1681,30 @@
 <dd><strong>Relevant CSS Property</strong>: "background-color"
 
 
-<!--
-<dt><code title><dfn title=command-indent>indent</dfn></code>
--->
+<dt><code title=""><dfn id=command-indent title=command-indent>indent</dfn></code>
 <!--
 IE9: Outputs <blockquote style="margin-right: 0px" dir="ltr">, or when
-  surrounding RTL blocks, <blockquote style="margin-left: 0px" dir="rtl">.
+  surrounding RTL blocks, <blockquote style="margin-left: 0px" dir="rtl">.  The
+  direction seems to go by the end of the selection.  The presence of the dir
+  attribute means that any contents that were inheriting a different dir from
+  an ancestor get their direction changed as a side effect, but if they
+  actually have the opposite dir specified, they won't appear to be indented.
+  It doesn't reset top or bottom margins on the blockquote, so it adds them.
+  If it's not wrapping a block element, like if it's only wrapping up until a
+  <br>, it adds a <p>.
 Firefox 4.0: In styleWithCSS mode, adds style="margin-left: 40px" to the
   appropriate block container (or margin-right if it's RTL).  If there's no
-  appropriate block container, adds a div.  In non-styleWithCSS mode, uses
-  <blockquote>, so it indents on both sides.
+  appropriate block container, adds a div.  If multiple blocks are affected, it
+  goes by the direction of the block whose style it's changing, which winds up
+  being wrong for descendants with different direction.  In non-styleWithCSS
+  mode, uses <blockquote>, so it indents on both sides and also adds top/bottom
+  margins.
 Chrome 12 dev: Outputs <blockquote class="webkit-indent-blockquote"
   style="margin: 0 0 0 40px; border: none; padding: 0px"> in both modes for
   both LTR and RTL (which is broken for RTL, since it indents only on the
-  right).
-Opera 11.00: Outputs <blockquote>, so it indents on both sides.
+  left).
+Opera 11.00: Outputs <blockquote>, so it indents on both sides and on the
+  top/bottom.
 
 For repeated indentation, everyone except Opera that outputs <blockquote>s just
 puts them at the outermost possible location, which works well.  Opera puts
@@ -1707,15 +1716,21 @@
 doesn't actually increase the indentation.  However, if an element has an
 explicit left margin (assuming LTR), it will increase the margin to 80px, so it
 works with WebKit's blockquotes.
+
+
+We have two strategies for handling directionality: always indent on both sides
+(Firefox non-CSS, Opera) or try to figure out heuristically which side we want
+(IE, Firefox CSS).  The latter approach is only possible by adding extra markup
+and complexity, so for now we'll take the easy way out and go with just
+indenting on both sides.
 -->
 
-<!--
 <dd><strong>Action</strong>:
 
 <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 <span data-anolis-spec=domrange title=concept-range-start>start</span> and <span data-anolis-spec=domrange title=concept-range-end>end</span> <span data-anolis-spec=domrange title=concept-boundary-point-node>nodes</span>
-  and <span data-anolis-spec=domrange title=concept-boundary-point-offset>offsets</span> of the <span data-anolis-spec=domrange title=concept-range>range</span>.
+  <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 . . .
@@ -1723,22 +1738,22 @@
   <li>Repeat the following steps:
 
   <ol>
-    <li>If <var title>start node</var> is a <code data-anolis-spec=domcore>Text</code> or <code data-anolis-spec=domcore>Comment</code> node or
-    <var title>start offset</var> is 0, set <var title>start offset</var> to the <span data-anolis-spec=domrange title=concept-indexof>index</span>
-    of <var title>start node</var> and then set <var title>start node</var> to its
-    <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
-
-    <li>Otherwise, if the <var title>start offset</var> is equal to the
-    <span data-anolis-spec=domrange title=concept-node-length>length</span> of <var title>start node</var>, set <var title>start offset</var> to one
-    plus the <span data-anolis-spec=domrange title=concept-indexof>index</span> of <var title>start node</var> and then set <var title>start
-    node</var> to its <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
-
-    <li>Otherwise, if the <span data-anolis-spec=domcore title=concept-tree-child>child</span> of <var title>start node</var> with <span data-anolis-spec=domrange title=concept-indexof>index</span>
-    <var title>start offset</var> minus one is a <code data-anolis-spec=domcore>Text</code> or <code data-anolis-spec=domcore>Comment</code> node, or an
-    (insert definition here), subtract one from <var title>start offset</var>.
+    <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>.  As elsewhere, we have trouble with the exact definition because
+    &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.
 
@@ -1748,28 +1763,90 @@
   <li>Repeat the following steps:
 
   <ol>
-    <li>If <var title>end node</var> is a <code data-anolis-spec=domcore>Text</code> or <code data-anolis-spec=domcore>Comment</code> node or <var title>end
-    offset</var> is 0, set <var title>end offset</var> to the <span data-anolis-spec=domrange title=concept-indexof>index</span> of <var title>end
-    node</var> and then set <var title>end node</var> to its <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
-
-    <li>Otherwise, if the <var title>end offset</var> is equal to the <span data-anolis-spec=domrange title=concept-node-length>length</span>
-    of <var title>end node</var>, set <var title>end offset</var> to one plus the <span data-anolis-spec=domrange title=concept-indexof>index</span>
-    of <var title>end node</var> and then set <var title>end node</var> to its <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
-
-    <li>Otherwise, if the <span data-anolis-spec=domcore title=concept-tree-child>child</span> of <var title>end node</var> with <span data-anolis-spec=domrange title=concept-indexof>index</span>
-    <var title>end offset</var> plus one is a <code data-anolis-spec=domcore>Text</code> or <code data-anolis-spec=domcore>Comment</code> node, or an
-    (insert definition here), add one to <var title>end offset</var>.
+    <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.
   </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>For each <var title="">node</var> in <var title="">node list</var>:
+
+  <ol>
+    <li>If the <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> of <var title="">node</var> is an <a href=#html-element>HTML
+    element</a>; its "display" property computes to "block"; its
+    "margin-left" and "margin-right" properties compute to "40px"; and its
+    "margin-top" and "margin-bottom" properties compute to "0"; then 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 <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>,
+    <a href=#preserving-ranges>preserving ranges</a>, and continue with the next <var title="">node</var>.
+
+    <li>Let <var title="">tag</var> be "div" if the <a href=#css-styling-flag>CSS styling flag</a> is
+    true, otherwise "blockquote".
+    <!-- Firefox 4.0 is the only tested browser that respects the CSS styling
+    flag for indent at all.  For indent, as for inline markup commands like
+    bold, it will modify the inline style of existing elements if available,
+    and only create spans/divs if there are no existing elements where it could
+    add the style instead.  For indent as for other commands, I follow WebKit
+    and always create an extra element, to ensure consistency between CSS and
+    non-CSS modes. -->
+
+    <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(<var title="">tag</var>)</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>Set the CSS property "margin" of <var title="">new parent</var> to "0 40px".
+
+    <p class=XXX>This indents on both sides, so we don't have to worry about
+    directionality.  Preferably we should indent only on the start side, but
+    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).
+
+    <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>.
+  </ol>
 </ol>
 
 <dd><strong>State</strong>:
 
 <dd><strong>Value</strong>:
--->
 
 
 <dt><code title=""><dfn id=command-inserthorizontalrule title=command-inserthorizontalrule>insertHorizontalRule</dfn></code>
--- a/implementation.js	Thu Apr 14 10:11:40 2011 -0600
+++ b/implementation.js	Thu Apr 28 12:49:11 2011 -0600
@@ -1896,7 +1896,7 @@
 		// "medium", "large", "x-large", "xx-large", "xxx-large", and is not a
 		// valid CSS absolute length, then do nothing and abort these steps."
 		//
-		// More cheap hacks to skip of valid CSS absolute length checks.
+		// More cheap hacks to skip valid CSS absolute length checks.
 		if (["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"].indexOf(value) == -1
 		&& !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc)$/.test(value)) {
 			return;
@@ -1938,6 +1938,155 @@
 		}
 		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;
+
+			// "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, 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 (var node = newRange.startContainer; node != nextNodeDescendants(newRange.endContainer); node = nextNode(node)) {
+			if (!isContained(node, newRange)) {
+				continue;
+			}
+
+			if (node.nodeType == Node.ELEMENT_NODE
+			&& ["TBODY", "THEAD", "TR", "TH", "TD"].indexOf(node.tagName) != -1) {
+				continue;
+			}
+
+			if (nodeList.length
+			&& isAncestor(nodeList[nodeList.length - 1], node)) {
+				continue;
+			}
+
+			nodeList.push(node);
+		}
+
+		// "For each node in node list:"
+		for (var i = 0; i < nodeList.length; i++) {
+			var node = nodeList[i];
+
+			// "If the previousSibling of node is an HTML element; its
+			// "display" property computes to "block"; its "margin-left" and
+			// "margin-right" properties compute to "40px"; and its
+			// "margin-top" and "margin-bottom" properties compute to "0"; then
+			// append node as the last child of its previousSibling, preserving
+			// ranges, and continue with the next node."
+			if (isHtmlElement(node.previousSibling)) {
+				var style = getComputedStyle(node.previousSibling);
+				if (style.display == "block"
+				&& style.marginLeft == "40px"
+				&& style.marginRight == "40px"
+				&& style.marginTop == "0px"
+				&& style.marginBottom == "0px") {
+					movePreservingRanges(node, node.previousSibling, node.previousSibling.childNodes.length);
+					continue;
+				}
+			}
+
+			// "Let tag be "div" if the CSS styling flag is true, otherwise
+			// "blockquote"."
+			var tag = cssStylingFlag ? "div" : "blockquote";
+
+			// "Let new parent be the result of calling createElement(tag) on
+			// the ownerDocument of node."
+			var newParent = node.ownerDocument.createElement(tag);
+
+			// "Insert new parent into node's parent immediately before node."
+			node.parentNode.insertBefore(newParent, node);
+
+			// "Set the CSS property "margin" of new parent to "0 40px"."
+			newParent.setAttribute("style", "margin: 0 40px");
+
+			// "Append node as the last child of new parent, preserving
+			// ranges."
+			movePreservingRanges(node, newParent, 0);
+		}
+
+		break;
+
 		case "inserthorizontalrule":
 		// "Run deleteContents() on the range."
 		range.deleteContents();
--- a/preprocess	Thu Apr 14 10:11:40 2011 -0600
+++ b/preprocess	Thu Apr 28 12:49:11 2011 -0600
@@ -11,6 +11,8 @@
     'attrlocalname': '<span data-anolis-spec=domcore title=concept-attr-local-name>local name</span>',
     'attrvalue': '<span data-anolis-spec=domcore title=concept-attr-value>value</span>',
     'b': '<code data-anolis-spec=html title="the b element">b</code>',
+    'blockquote': '<code data-anolis-spec=html title="the blockquote element">blockquote</code>',
+    'br': '<code data-anolis-spec=html title="the br element">br</code>',
     'boundarypoint': '<span data-anolis-spec=domrange title=concept-boundary-point>boundary point</span>',
     '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>',
--- a/source.html	Thu Apr 14 10:11:40 2011 -0600
+++ b/source.html	Thu Apr 28 12:49:11 2011 -0600
@@ -1697,21 +1697,30 @@
 <dd><strong>Relevant CSS Property</strong>: "background-color"
 
 
-<!--
 <dt><code title><dfn title=command-indent>indent</dfn></code>
--->
 <!--
 IE9: Outputs <blockquote style="margin-right: 0px" dir="ltr">, or when
-  surrounding RTL blocks, <blockquote style="margin-left: 0px" dir="rtl">.
+  surrounding RTL blocks, <blockquote style="margin-left: 0px" dir="rtl">.  The
+  direction seems to go by the end of the selection.  The presence of the dir
+  attribute means that any contents that were inheriting a different dir from
+  an ancestor get their direction changed as a side effect, but if they
+  actually have the opposite dir specified, they won't appear to be indented.
+  It doesn't reset top or bottom margins on the blockquote, so it adds them.
+  If it's not wrapping a block element, like if it's only wrapping up until a
+  <br>, it adds a <p>.
 Firefox 4.0: In styleWithCSS mode, adds style="margin-left: 40px" to the
   appropriate block container (or margin-right if it's RTL).  If there's no
-  appropriate block container, adds a div.  In non-styleWithCSS mode, uses
-  <blockquote>, so it indents on both sides.
+  appropriate block container, adds a div.  If multiple blocks are affected, it
+  goes by the direction of the block whose style it's changing, which winds up
+  being wrong for descendants with different direction.  In non-styleWithCSS
+  mode, uses <blockquote>, so it indents on both sides and also adds top/bottom
+  margins.
 Chrome 12 dev: Outputs <blockquote class="webkit-indent-blockquote"
   style="margin: 0 0 0 40px; border: none; padding: 0px"> in both modes for
   both LTR and RTL (which is broken for RTL, since it indents only on the
-  right).
-Opera 11.00: Outputs <blockquote>, so it indents on both sides.
+  left).
+Opera 11.00: Outputs <blockquote>, so it indents on both sides and on the
+  top/bottom.
 
 For repeated indentation, everyone except Opera that outputs <blockquote>s just
 puts them at the outermost possible location, which works well.  Opera puts
@@ -1723,9 +1732,15 @@
 doesn't actually increase the indentation.  However, if an element has an
 explicit left margin (assuming LTR), it will increase the margin to 80px, so it
 works with WebKit's blockquotes.
+
+
+We have two strategies for handling directionality: always indent on both sides
+(Firefox non-CSS, Opera) or try to figure out heuristically which side we want
+(IE, Firefox CSS).  The latter approach is only possible by adding extra markup
+and complexity, so for now we'll take the easy way out and go with just
+indenting on both sides.
 -->
 
-<!--
 <dd><strong>Action</strong>:
 
 <ol>
@@ -1744,7 +1759,7 @@
     of <var>start node</var> and then set <var>start node</var> to its
     [[parent]].
 
-    <li>Otherwise, if the <var>start offset</var> is equal to the
+    <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]].
@@ -1764,28 +1779,92 @@
   <li>Repeat the following steps:
 
   <ol>
-    <li>If <var>end node</var> is a [[text]] or [[comment]] node or <var>end
-    offset</var> is 0, set <var>end offset</var> to the [[index]] of <var>end
+    <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 <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> plus one is a [[text]] or [[comment]] node, or an
-    (insert definition here), add one to <var>end offset</var>.
+    <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.
   </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>For each <var>node</var> in <var>node list</var>:
+
+  <ol>
+    <li>If the [[previoussibling]] of <var>node</var> is an <span>HTML
+    element</span>; its "display" property computes to "block"; its
+    "margin-left" and "margin-right" properties compute to "40px"; and its
+    "margin-top" and "margin-bottom" properties compute to "0"; then append
+    <var>node</var> as the last [[child]] of its [[previoussibling]],
+    <span>preserving ranges</span>, and continue with the next <var>node</var>.
+
+    <li>Let <var>tag</var> be "div" if the <span>CSS styling flag</span> is
+    true, otherwise "blockquote".
+    <!-- Firefox 4.0 is the only tested browser that respects the CSS styling
+    flag for indent at all.  For indent, as for inline markup commands like
+    bold, it will modify the inline style of existing elements if available,
+    and only create spans/divs if there are no existing elements where it could
+    add the style instead.  For indent as for other commands, I follow WebKit
+    and always create an extra element, to ensure consistency between CSS and
+    non-CSS modes. -->
+
+    <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> into <var>node</var>'s [[parent]]
+    immediately before <var>node</var>.
+
+    <li>Set the CSS property "margin" of <var>new parent</var> to "0 40px".
+
+    <p class=XXX>This indents on both sides, so we don't have to worry about
+    directionality.  Preferably we should indent only on the start side, but
+    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).
+
+    <li>Append <var>node</var> as the last [[child]] of <var>new parent</var>,
+    <span>preserving ranges</span>.
+  </ol>
 </ol>
 
 <dd><strong>State</strong>:
 
 <dd><strong>Value</strong>:
--->
 
 
 <dt><code title><dfn title=command-inserthorizontalrule>insertHorizontalRule</dfn></code>