Usable outdent spec
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Sun, 01 May 2011 15:04:13 -0600
changeset 74 75fbcc5872a0
parent 73 e6eebb4728bf
child 75 29c187d9d400
Usable outdent spec
autoimplementation.html
editcommands.html
implementation.js
source.html
--- a/autoimplementation.html	Thu Apr 28 15:01:23 2011 -0600
+++ b/autoimplementation.html	Sun May 01 15:04:13 2011 -0600
@@ -621,10 +621,10 @@
 		'<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',
+		'<blockquote style="margin-right: 0px;" dir="ltr"><p>foo[bar]</p><p>baz</p></blockquote><p>extra',
+		'<blockquote style="margin-right: 0px;" dir="ltr"><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
+		'<blockquote style="margin-right: 0px;" dir="ltr"><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
+		'<blockquote style="margin-right: 0px;" 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',
@@ -633,16 +633,34 @@
 		'<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',
+		'<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',
+		'<div style="margin: 0 40px"><p>foo[bar]</p><p>baz</p></div><p>extra',
+		'<div style="margin: 0 40px"><p>foo[bar</p><p>b]az</p></div><p>extra',
+		'<div style="margin: 0 40px"><p>foo[bar]</p></div><p>baz</p><p>extra',
+		'<div style="margin: 0 40px"><p>foo[bar</p></div><p>b]az</p><p>extra',
+
+		// Now let's try nesting lots of stuff and see what happens.
+		'<blockquote><blockquote>foo[bar]baz</blockquote></blockquote>',
+		'<blockquote><blockquote data-abc=def>foo[bar]baz</blockquote></blockquote>',
+		'<blockquote data-abc=def><blockquote>foo[bar]baz</blockquote></blockquote>',
+		'<blockquote><div><p>foo[bar]<p>baz</div></blockquote>',
+		'<blockquote><div id=abc><p>foo[bar]<p>baz</div></blockquote>',
+		'<blockquote id=abc><p>foo[bar]<p>baz</blockquote>',
+		'<blockquote style="color: red"><p>foo[bar]<p>baz</blockquote>',
+		'<blockquote><p><b>foo[bar]</b><p>baz</blockquote>',
+		'<blockquote><p><strong>foo[bar]</strong><p>baz</blockquote>',
+		'<blockquote><p><span>foo[bar]</span><p>baz</blockquote>',
+		'<blockquote><blockquote style="color: red"><p>foo[bar]</blockquote><p>baz</blockquote>',
+		'<blockquote style="color: red"><blockquote><p>foo[bar]</blockquote><p>baz</blockquote>',
 	],
 	removeformat: [
 		'foo[]bar',
@@ -1102,10 +1120,12 @@
 
 	var test;
 	var inputs = document.getElementById(command).getElementsByTagName("input");
+	// Strip \u200B (zero-width space) so copy-pasting from the cells works
+	// non-confusingly
 	if (inputs.length == 1) {
-		test = inputs[0].value;
+		test = inputs[0].value.replace(/\u200B/g, "");
 	} else {
-		test = [inputs[1].value, inputs[0].value];
+		test = [inputs[1].value, inputs[0].value.replace(/\u200B/g, "")];
 	}
 
 	doInputCell(tr, test);
--- a/editcommands.html	Thu Apr 28 15:01:23 2011 -0600
+++ b/editcommands.html	Sun May 01 15:04:13 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-28-april-2011>Work in Progress &mdash; Last Update 28 April 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-1-may-2011>Work in Progress &mdash; Last Update 1 May 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;[email protected]&gt;
@@ -185,6 +185,51 @@
 <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>To <dfn id=set-the-tag-name>set the tag name</dfn> of an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> <var title="">element</var> to
+<var title="">new name</var>, the user agent must run the following steps:
+
+<ol>
+  <li>If <var title="">element</var> is an <a href=#html-element>HTML element</a> with <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a>
+  equal to <var title="">new name</var>, return <var title="">element</var>.
+
+  <li>If <var title="">element</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is null, return <var title="">element</var>.
+
+  <li>Let <var title="">replacement element</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="">new name</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="">element</var>.
+
+  <li>Insert <var title="">replacement element</var> into <var title="">element</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="">element</var>.
+
+  <li>Copy all attributes of <var title="">element</var> to <var title="">replacement
+  element</var>, in order.
+
+  <li>While <var title="">element</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, append the first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of
+  <var title="">element</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="">replacement element</var>,
+  <a href=#preserving-ranges>preserving ranges</a>.
+
+  <li>Remove <var title="">element</var> from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+  <li>Return <var title="">replacement element</var>.
+</ol>
+
+<p>To remove a node <var title="">node</var> while <dfn id=preserving-its-descendants>preserving its
+descendants</dfn>:
+
+<ol>
+  <li>Let <var title="">children</var> be a list of <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>.
+
+  <li>If <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is null, remove all of <var title="">node</var>'s
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a> from <var title="">node</var>, then return <var title="">children</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>, insert the first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of
+  <var title="">node</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>, <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>Return <var title="">children</var>.
+</ol>
+
 <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>
@@ -1835,54 +1880,56 @@
   <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>:
-
-  <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
-    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>.
-  </ol>
+  <li><a href=#indent>Indent</a> each member of <var title="">node list</var>.
+</ol>
+
+<p>To <dfn id=indent>indent</dfn> 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>:
+
+<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>, then abort these steps.
+
+  <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
+  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>.
 </ol>
 
 <dd><strong>State</strong>:
@@ -2029,93 +2076,177 @@
   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)
-  -->
+  range</var> that have no <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>.
+
+  <li><a href=#outdent>Outdent</a> each member of <var title="">node list</var>.
+</ol>
+
+<p>To <dfn id=outdent>outdent</dfn> 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>:
+
+<!--
+Things that are produced for indentation that we need to consider removing:
+
+* 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>
+  <li>If <var title="">node</var> is not <a href=#editable>editable</a>, abort these steps.
+
+  <p class=XXX>Handle this better for nested editable/non-editable.
+
+  <!-- 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 an <a href=#indentation-element>indentation element</a>:
+
   <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>
+    <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>, unset all attributes of <var title="">node</var>, then <a href=#set-the-tag-name>set the
+    tag name</a> of <var title="">node</var> to "div".
+    <!-- In this case, removing it entirely will result in its first or last
+    child becoming part of the previous or next sibling's line box, so we
+    want to keep a div. -->
+
+    <li>Otherwise, remove <var title="">node</var>, <a href=#preserving-its-descendants>preserving its
+    descendants</a>.
+
+    <li>Abort these steps.
   </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>.
+  <li>If <var title="">node</var> is a <a href=#potential-indentation-element>potential indentation element</a>:
+  <!-- This might be an indentation element that had style added to it by
+  Firefox in CSS mode, for instance (color, font-family, etc.). -->
+
+  <ol>
+    <li><a href=#set-the-tag-name>Set the tag name</a> of <var title="">node</var> to "div".
+
+    <li>Unset the <code class=external data-anolis-spec=html title=classes><a href=http://www.whatwg.org/html/#classes>class</a></code> and
+    <code class=external data-anolis-spec=html title="the dir attribute"><a href=http://www.whatwg.org/html/#the-dir-attribute>dir</a></code>
+    attributes of <var title="">node</var>, if any.
+
+    <li>Unset the margin, padding, and border CSS properties of
+    <var title="">node</var>.
+
+    <li>Abort these steps.
+  </ol>
+
+  <!-- Approximate algorithms when an ancestor is causing the indentation
+  appear to be:
+
+  IE9: Go to the innermost element causing indentation.  If the stuff to be
+    outdented includes all the contents of that element, get rid of it, but
+    if it has any attributes, change it to a <p> with those same attributes.
+    This is an excellent idea in general, but unfortunately it preserves
+    explicitly-specified margins in style attributes, which isn't great.  In
+    other cases, it moves the stuff to be outdented outside.  Not clear on
+    all the details, seems to be pretty confusing.  Also does a bunch of
+    seemingly arbitrary normalization like removing divs and some attributes
+    from some things . . .
+  Firefox 4.0: Go to the innermost element causing indentation.  If the stuff
+    to be outdented includes all the contents of that element, get rid of it,
+    even if it has arbitrary attributes.  Otherwise, move the stuff to be
+    outdented outside the indenting element.  If there are any intervening
+    elements that include stuff not to be outdented, wrap the outdented stuff
+    in copies (which can duplicate id's, etc.).
+  Chrome 12 dev: Go to the outermost element causing indentation (even if the
+    current element is itself causing indentation).  Move the text to be
+    outdented outside that outermost element, without regard to any
+    intervening elements.  Then recreate the original styles on the moved
+    text, in some fashion.  Something like that; it confuses me and doesn't
+    seem to be reasonable.
+  Opera 11.00: Like Firefox, except it goes to the outermost element, not the
+    innermost.  Also seems to special-case to avoid duplicate id's, and has a
+    few other quirks.
+
+  Overall, all flawed, so I'll make up my own, patterned after pushing down
+  styles.  First we search ancestors for an indentation element, which we stand
+  a chance of completely removing.  Failing that, we look for a potential
+  indentation element, which we cannot completely remove. -->
+  <li>Let <var title="">current ancestor</var> be <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+  <li>Let <var title="">ancestor 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>While <var title="">current ancestor</var> is an <a href=#editable>editable</a> <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>
+  that is not an <a href=#indentation-element>indentation element</a>, append <var title="">current
+  ancestor</var> to <var title="">ancestor list</var> and then set <var title="">current
+  ancestor</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+  <li>If <var title="">current ancestor</var> is not an <a href=#editable>editable</a>
+  <a href=#indentation-element>indentation element</a>:
+
+  <ol>
+    <li>Let <var title="">current ancestor</var> be <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+    <li>Let <var title="">ancestor list</var> be the empty list.
+
+    <li>While <var title="">current ancestor</var> is an <a href=#editable>editable</a>
+    <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> that is not a <a href=#potential-indentation-element>potential indentation element</a>,
+    append <var title="">current ancestor</var> to <var title="">ancestor list</var> and then set
+    <var title="">current ancestor</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+  </ol>
+
+  <li>If <var title="">current ancestor</var> is not an <a href=#editable>editable</a>
+  <a href=#potential-indentation-element>potential indentation element</a>, abort these steps.
+
+  <!-- At this point, we have an ancestor to split up. -->
+  <li>Append <var title="">current ancestor</var> to <var title="">ancestor list</var>.
+
+  <li>Let <var title="">original ancestor</var> be <var title="">current ancestor</var>.
+  <!-- We can't outdent it yet, because we need its children to remain intact
+  for the loop. -->
+
+  <li>While <var title="">ancestor list</var> is not empty:
+
+  <ol>
+    <li>Let <var title="">current ancestor</var> be the last member of <var title="">ancestor
+    list</var>.
+
+    <li>Remove the last member of <var title="">ancestor list</var>.
+
+    <li>Let <var title="">children</var> be the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a> of <var title="">current
+    ancestor</var>.
+
+    <li>For each <var title="">child</var> in <var title="">children</var>, if <var title="">child</var>
+    is neither <var title="">node</var> nor the last member of <var title="">ancestor list</var>,
+    <a href=#indent>indent</a> <var title="">child</var>.
+  </ol>
+
+  <li><a href=#outdent>Outdent</a> <var title="">original ancestor</var>.
 </ol>
 
+<p>A <dfn id=potential-indentation-element>potential indentation element</dfn> is either 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>, or 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> that has 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 "margin" or some subproperty
+of it.
+
+<p>An <dfn id=indentation-element>indentation element</dfn> is an <a href=#potential-indentation-element>potential indentation
+element</a> that has no attributes other than one or more of
+
+<ul>
+  <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;
+
+  <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.
+</ul>
+
 <dd><strong>State</strong>:
 
 <dd><strong>Value</strong>:
--- a/implementation.js	Thu Apr 28 15:01:23 2011 -0600
+++ b/implementation.js	Sun May 01 15:04:13 2011 -0600
@@ -336,6 +336,70 @@
 		&& ["inline", "inline-block", "inline-table"].indexOf(getComputedStyle(node).display) != -1));
 }
 
+function setTagName(element, newName) {
+	// "If element is an HTML element with local name equal to new name, return
+	// element."
+	if (isHtmlElement(element) && element.tagName == newName.toUpperCase()) {
+		return element;
+	}
+
+	// "If element's parent is null, return element."
+	if (!element.parentNode) {
+		return element;
+	}
+
+	// "Let replacement element be the result of calling createElement(new
+	// name) on the ownerDocument of element."
+	var replacementElement = element.ownerDocument.createElement(newName);
+
+	// "Insert replacement element into element's parent immediately before
+	// element."
+	element.parentNode.insertBefore(replacementElement, element);
+
+	// "Copy all attributes of element to replacement element, in order."
+	for (var i = 0; i < element.attributes.length; i++) {
+		replacementElement.setAttributeNS(element.attributes[i].namespaceURI, element.attributes[i].name, element.attributes[i].value);
+	}
+
+	// "While element has children, append the first child of element as the
+	// last child of replacement element, preserving ranges."
+	while (element.childNodes.length) {
+		movePreservingRanges(element.firstChild, replacementElement, replacementElement.childNodes.length);
+	}
+
+	// "Remove element from its parent."
+	element.parentNode.removeChild(element);
+
+	// "Return replacement element."
+	return replacementElement;
+}
+
+function removePreservingDescendants(node) {
+	// "Let children be a list of node's children."
+	var children = [].slice.call(node.childNodes);
+
+	// "If node's parent is null, remove all of node's children from node, then
+	// return children."
+	if (!node.parentNode) {
+		while (node.hasChildNodes()) {
+			node.removeChild(node.firstChild);
+		}
+		return children;
+	}
+
+	// "While node has children, insert the first child of node into node's
+	// parent immediately before node, preserving ranges."
+	while (node.hasChildNodes()) {
+		movePreservingRanges(node.firstChild, node.parentNode, getNodeIndex(node));
+	}
+
+	// "Remove node from its parent."
+	node.parentNode.removeChild(node);
+
+	// "Return children."
+	return children;
+}
+
 // "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
@@ -2073,45 +2137,9 @@
 			nodeList.push(node);
 		}
 
-		// "For each node in node list:"
+		// "Indent each member of 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);
+			indentNode(nodeList[i]);
 		}
 		break;
 
@@ -2271,8 +2299,8 @@
 		// "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."
+		// "Let node list be all nodes contained in new range that have no
+		// children."
 		var nodeList = [];
 		for (
 			var node = newRange.startContainer;
@@ -2280,74 +2308,14 @@
 			node = nextNode(node)
 		) {
 			if (isContained(node, newRange)
-			&& !isContained(node.parentNode, newRange)) {
+			&& !node.hasChildNodes()) {
 				nodeList.push(node);
 			}
 		}
 
-		// "For each node in node list:"
+		// "Outdent each member of 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));
-			}
+			outdentNode(nodeList[i]);
 		}
 		break;
 
@@ -2517,6 +2485,224 @@
 	}
 }
 
+function indentNode(node) {
+	// "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, then abort these steps."
+	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);
+			return;
+		}
+	}
+
+	// "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);
+}
+
+function outdentNode(node) {
+	// "If node is not editable, abort these steps."
+	if (!isEditable(node)) {
+		return;
+	}
+
+	// "If node is an indentation element:"
+	if (isIndentationElement(node)) {
+		// "If node's last child and nextSibling are both inline nodes or its
+		// first child and previousSibling are both inline nodes, unset all
+		// attributes of node, then set the tag name of node to "div"."
+		if ((isInlineNode(node.lastChild) && isInlineNode(node.nextSibling))
+		|| (isInlineNode(node.firstChild) && isInlineNode(node.previousSibling))) {
+			while (node.attributes.length) {
+				node.removeAttribute(node.attributes[0].name);
+			}
+
+			setTagName(node, "div");
+
+		// "Otherwise, remove node, preserving its descendants."
+		} else {
+			removePreservingDescendants(node);
+		}
+
+		// "Abort these steps."
+		return;
+	}
+
+	// "If node is a potential indentation element:"
+	if (isPotentialIndentationElement(node)) {
+		// "Set the tag name of node to "div"."
+		setTagName(node, "div");
+
+		// "Unset the class and dir attributes of node, if any."
+		node.removeAttribute("class");
+		node.removeAttribute("dir");
+
+		// "Unset the margin, padding, and border CSS properties of node."
+		node.style.margin = "";
+		node.style.padding = "";
+		node.style.border = "";
+		if (node.getAttribute("style") == "") {
+			node.removeAttribute("style");
+		}
+
+		// "Abort these steps."
+		return;
+	}
+
+	// "Let current ancestor be node's parent."
+	var currentAncestor = node.parentNode;
+
+	// "Let ancestor list be a list of nodes, initially empty."
+	var ancestorList = [];
+
+	// "While current ancestor is an editable Element that is not an
+	// indentation element, append current ancestor to ancestor list and then
+	// set current ancestor to its parent."
+	while (isEditable(currentAncestor)
+	&& currentAncestor.nodeType == Node.ELEMENT_NODE
+	&& !isIndentationElement(currentAncestor)) {
+		ancestorList.push(currentAncestor);
+		currentAncestor = currentAncestor.parentNode;
+	}
+
+	// "If current ancestor is not an editable indentation element:"
+	if (!isEditable(currentAncestor)
+	|| !isIndentationElement(currentAncestor)) {
+		// "Let current ancestor be node's parent."
+		currentAncestor = node.parentNode;
+
+		// "Let ancestor list be the empty list."
+		ancestorList = [];
+
+		// "While current ancestor is an editable Element that is not a
+		// potential indentation element, append current ancestor to ancestor
+		// list and then set current ancestor to its parent."
+		while (isEditable(currentAncestor)
+		&& currentAncestor.nodeType == Node.ELEMENT_NODE
+		&& !isPotentialIndentationElement(currentAncestor)) {
+			ancestorList.push(currentAncestor);
+			currentAncestor = currentAncestor.parentNode;
+		}
+	}
+
+	// "If current ancestor is not an editable potential indentation element,
+	// abort these steps."
+	if (!isEditable(currentAncestor)
+	|| !isPotentialIndentationElement(currentAncestor)) {
+		return;
+	}
+
+	// "Append current ancestor to ancestor list."
+	ancestorList.push(currentAncestor);
+
+	// "Let original ancestor be current ancestor."
+	var originalAncestor = currentAncestor;
+
+	// "While ancestor list is not empty:"
+	while (ancestorList.length) {
+		// "Let current ancestor be the last member of ancestor list."
+		//
+		// "Remove the last member of ancestor list."
+		currentAncestor = ancestorList.pop();
+
+		// "Let children be the children of current ancestor."
+		var children = [].slice.call(currentAncestor.childNodes);
+
+		// "For each child in children, if child is neither node nor the last
+		// member of ancestor list, indent child."
+		for (var i = 0; i < children.length; i++) {
+			var child = children[i];
+			if (child != node && child != ancestorList[ancestorList.length - 1]) {
+				indentNode(child);
+			}
+		}
+	}
+
+	// "Outdent original ancestor."
+	outdentNode(originalAncestor);
+}
+
+// "A potential indentation element is either a blockquote, or a div that has a
+// style attribute that sets "margin" or some subproperty of it."
+function isPotentialIndentationElement(node) {
+	if (!isHtmlElement(node)) {
+		return false;
+	}
+
+	if (node.tagName == "BLOCKQUOTE") {
+		return true;
+	}
+
+	if (node.tagName != "DIV") {
+		return false;
+	}
+
+	for (var i = 0; i < node.style.length; i++) {
+		// Approximate check
+		if (/^(-[a-z]+-)?margin/.test(node.style[i])) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+// "An indentation element is a potential indentation element that has no
+// attributes other than one or more of
+//
+//   * "a style attribute that sets no properties other than "margin", "border",
+//     "padding", or subproperties of those;
+//   * "a class attribute;
+//   * "a dir attribute."
+function isIndentationElement(node) {
+	if (!isPotentialIndentationElement(node)) {
+		return false;
+	}
+
+	if (node.tagName != "BLOCKQUOTE" && node.tagName != "DIV") {
+		return false;
+	}
+
+	for (var i = 0; i < node.attributes.length; i++) {
+		if (!isHtmlNamespace(node.attributes[i].namespaceURI)
+		|| ["style", "class", "dir"].indexOf(node.attributes[i].name) == -1) {
+			return false;
+		}
+	}
+
+	for (var i = 0; i < node.style.length; i++) {
+		// This is approximate, but it works well enough for my purposes.
+		if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
 function myQueryCommandState(command) {
 	command = command.toLowerCase();
 
--- a/source.html	Thu Apr 28 15:01:23 2011 -0600
+++ b/source.html	Sun May 01 15:04:13 2011 -0600
@@ -172,6 +172,53 @@
 <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>To <dfn>set the tag name</dfn> of an [[element]] <var>element</var> to
+<var>new name</var>, the user agent must run the following steps:
+
+<ol>
+  <li>If <var>element</var> is an <span>HTML element</span> with [[localname]]
+  equal to <var>new name</var>, return <var>element</var>.
+
+  <li>If <var>element</var>'s [[parent]] is null, return <var>element</var>.
+
+  <li>Let <var>replacement element</var> be the result of calling <code
+  data-anolis-spec=domcore
+  title=dom-Document-createElement>createElement(<var>new name</var>)</code> on
+  the [[ownerdocument]] of <var>element</var>.
+
+  <li>Insert <var>replacement element</var> into <var>element</var>'s
+  [[parent]] immediately before <var>element</var>.
+
+  <li>Copy all attributes of <var>element</var> to <var>replacement
+  element</var>, in order.
+
+  <li>While <var>element</var> has [[children]], append the first [[child]] of
+  <var>element</var> as the last [[child]] of <var>replacement element</var>,
+  <span>preserving ranges</span>.
+
+  <li>Remove <var>element</var> from its [[parent]].
+
+  <li>Return <var>replacement element</var>.
+</ol>
+
+<p>To remove a node <var>node</var> while <dfn>preserving its
+descendants</dfn>:
+
+<ol>
+  <li>Let <var>children</var> be a list of <var>node</var>'s [[children]].
+
+  <li>If <var>node</var>'s [[parent]] is null, remove all of <var>node</var>'s
+  [[children]] from <var>node</var>, then return <var>children</var>.
+
+  <li>While <var>node</var> has [[children]], insert the first [[child]] of
+  <var>node</var> into <var>node</var>'s [[parent]] immediately before
+  <var>node</var>, <span>preserving ranges</span>.
+
+  <li>Remove <var>node</var> from its [[parent]].
+
+  <li>Return <var>children</var>.
+</ol>
+
 <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
@@ -1850,56 +1897,58 @@
   [[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>:
-
-  <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
-    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>.
-  </ol>
+  <li><span>Indent</span> each member of <var>node list</var>.
+</ol>
+
+<p>To <dfn>indent</dfn> a [[node]] <var>node</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>, then abort these steps.
+
+  <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
+  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>.
 </ol>
 
 <dd><strong>State</strong>:
@@ -2065,95 +2114,177 @@
   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)
-  -->
+  range</var> that have no [[children]].
+
+  <li><span>Outdent</span> each member of <var>node list</var>.
+</ol>
+
+<p>To <dfn>outdent</dfn> a [[node]] <var>node</var>:
+
+<!--
+Things that are produced for indentation that we need to consider removing:
+
+* 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>
+  <li>If <var>node</var> is not <span>editable</span>, abort these steps.
+
+  <p class=XXX>Handle this better for nested editable/non-editable.
+
+  <!-- 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 an <span>indentation element</span>:
+
   <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>
+    <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>, unset all attributes of <var>node</var>, then <span>set the
+    tag name</span> of <var>node</var> to "div".
+    <!-- In this case, removing it entirely will result in its first or last
+    child becoming part of the previous or next sibling's line box, so we
+    want to keep a div. -->
+
+    <li>Otherwise, remove <var>node</var>, <span>preserving its
+    descendants</span>.
+
+    <li>Abort these steps.
   </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>.
+  <li>If <var>node</var> is a <span>potential indentation element</span>:
+  <!-- This might be an indentation element that had style added to it by
+  Firefox in CSS mode, for instance (color, font-family, etc.). -->
+
+  <ol>
+    <li><span>Set the tag name</span> of <var>node</var> to "div".
+
+    <li>Unset the <code data-anolis-spec=html title=classes>class</code> and
+    <code data-anolis-spec=html title="the dir attribute">dir</code>
+    attributes of <var>node</var>, if any.
+
+    <li>Unset the margin, padding, and border CSS properties of
+    <var>node</var>.
+
+    <li>Abort these steps.
+  </ol>
+
+  <!-- Approximate algorithms when an ancestor is causing the indentation
+  appear to be:
+
+  IE9: Go to the innermost element causing indentation.  If the stuff to be
+    outdented includes all the contents of that element, get rid of it, but
+    if it has any attributes, change it to a <p> with those same attributes.
+    This is an excellent idea in general, but unfortunately it preserves
+    explicitly-specified margins in style attributes, which isn't great.  In
+    other cases, it moves the stuff to be outdented outside.  Not clear on
+    all the details, seems to be pretty confusing.  Also does a bunch of
+    seemingly arbitrary normalization like removing divs and some attributes
+    from some things . . .
+  Firefox 4.0: Go to the innermost element causing indentation.  If the stuff
+    to be outdented includes all the contents of that element, get rid of it,
+    even if it has arbitrary attributes.  Otherwise, move the stuff to be
+    outdented outside the indenting element.  If there are any intervening
+    elements that include stuff not to be outdented, wrap the outdented stuff
+    in copies (which can duplicate id's, etc.).
+  Chrome 12 dev: Go to the outermost element causing indentation (even if the
+    current element is itself causing indentation).  Move the text to be
+    outdented outside that outermost element, without regard to any
+    intervening elements.  Then recreate the original styles on the moved
+    text, in some fashion.  Something like that; it confuses me and doesn't
+    seem to be reasonable.
+  Opera 11.00: Like Firefox, except it goes to the outermost element, not the
+    innermost.  Also seems to special-case to avoid duplicate id's, and has a
+    few other quirks.
+
+  Overall, all flawed, so I'll make up my own, patterned after pushing down
+  styles.  First we search ancestors for an indentation element, which we stand
+  a chance of completely removing.  Failing that, we look for a potential
+  indentation element, which we cannot completely remove. -->
+  <li>Let <var>current ancestor</var> be <var>node</var>'s [[parent]].
+
+  <li>Let <var>ancestor list</var> be a list of [[nodes]], initially empty.
+
+  <li>While <var>current ancestor</var> is an <span>editable</span> [[element]]
+  that is not an <span>indentation element</span>, append <var>current
+  ancestor</var> to <var>ancestor list</var> and then set <var>current
+  ancestor</var> to its [[parent]].
+
+  <li>If <var>current ancestor</var> is not an <span>editable</span>
+  <span>indentation element</span>:
+
+  <ol>
+    <li>Let <var>current ancestor</var> be <var>node</var>'s [[parent]].
+
+    <li>Let <var>ancestor list</var> be the empty list.
+
+    <li>While <var>current ancestor</var> is an <span>editable</span>
+    [[element]] that is not a <span>potential indentation element</span>,
+    append <var>current ancestor</var> to <var>ancestor list</var> and then set
+    <var>current ancestor</var> to its [[parent]].
+  </ol>
+
+  <li>If <var>current ancestor</var> is not an <span>editable</span>
+  <span>potential indentation element</span>, abort these steps.
+
+  <!-- At this point, we have an ancestor to split up. -->
+  <li>Append <var>current ancestor</var> to <var>ancestor list</var>.
+
+  <li>Let <var>original ancestor</var> be <var>current ancestor</var>.
+  <!-- We can't outdent it yet, because we need its children to remain intact
+  for the loop. -->
+
+  <li>While <var>ancestor list</var> is not empty:
+
+  <ol>
+    <li>Let <var>current ancestor</var> be the last member of <var>ancestor
+    list</var>.
+
+    <li>Remove the last member of <var>ancestor list</var>.
+
+    <li>Let <var>children</var> be the [[children]] of <var>current
+    ancestor</var>.
+
+    <li>For each <var>child</var> in <var>children</var>, if <var>child</var>
+    is neither <var>node</var> nor the last member of <var>ancestor list</var>,
+    <span>indent</span> <var>child</var>.
+  </ol>
+
+  <li><span>Outdent</span> <var>original ancestor</var>.
 </ol>
 
+<p>A <dfn>potential indentation element</dfn> is either a [[blockquote]], or a
+[[div]] that has a [[style]] attribute that sets "margin" or some subproperty
+of it.
+
+<p>An <dfn>indentation element</dfn> is an <span>potential indentation
+element</span> that has no attributes other than one or more of
+
+<ul>
+  <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;
+
+  <li>a <code data-anolis-spec=html title="the dir attribute">dir</code>
+  attribute.
+</ul>
+
 <dd><strong>State</strong>:
 
 <dd><strong>Value</strong>: