Initial justify* support
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Thu, 02 Jun 2011 14:34:46 -0600
changeset 224 620184e7a21e
parent 223 d949a407fd75
child 225 bb59ae1bbffd
Initial justify* support
autoimplementation.html
editcommands.html
implementation.js
source.html
--- a/autoimplementation.html	Thu Jun 02 12:43:16 2011 -0600
+++ b/autoimplementation.html	Thu Jun 02 14:34:46 2011 -0600
@@ -1455,6 +1455,162 @@
 		'foo [bar <i>baz] qoz</i> quz sic',
 		'foo bar <i>baz [qoz</i> quz] sic',
 	],
+	justifycenter: [
+		'foo[]bar<p>extra',
+		'<span>foo</span>{}<span>bar</span><p>extra',
+		'<span>foo[</span><span>]bar</span><p>extra',
+		'foo[bar]baz<p>extra',
+		'foo[bar<b>baz]qoz</b>quz<p>extra',
+		'<p>foo[]bar<p>extra',
+		'<p>foo[bar]baz<p>extra',
+		'<h1>foo[bar]baz</h1><p>extra',
+		'<pre>foo[bar]baz</pre><p>extra',
+		'<xmp>foo[bar]baz</xmp><p>extra',
+		'{<table><tr><td>foo<td>bar</table>}<p>extra',
+		'<center><p>[foo]<p>bar</center><p>extra',
+		'<center><p>[foo<p>bar]</center><p>extra',
+
+		'<div align=center><p>[foo]<p>bar</div><p>extra',
+		'<div align=center><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:center><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:center><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=justify><p>[foo]<p>bar</div><p>extra',
+		'<div align=justify><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:justify><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:justify><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=left><p>[foo]<p>bar</div><p>extra',
+		'<div align=left><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:left><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:left><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=right><p>[foo]<p>bar</div><p>extra',
+		'<div align=right><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:right><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:right><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=nonsense><p>[foo]</div><p>extra',
+		'<div style=text-align:inherit><p>[foo]</div><p>extra',
+		'<quasit align=right><p>[foo]</p></quasit><p>extra',
+	],
+	justifyfull: [
+		'foo[]bar<p>extra',
+		'<span>foo</span>{}<span>bar</span><p>extra',
+		'<span>foo[</span><span>]bar</span><p>extra',
+		'foo[bar]baz<p>extra',
+		'foo[bar<b>baz]qoz</b>quz<p>extra',
+		'<p>foo[]bar<p>extra',
+		'<p>foo[bar]baz<p>extra',
+		'<h1>foo[bar]baz</h1><p>extra',
+		'<pre>foo[bar]baz</pre><p>extra',
+		'<xmp>foo[bar]baz</xmp><p>extra',
+		'{<table><tr><td>foo<td>bar</table>}<p>extra',
+		'<center><p>[foo]<p>bar</center><p>extra',
+		'<center><p>[foo<p>bar]</center><p>extra',
+
+		'<div align=center><p>[foo]<p>bar</div><p>extra',
+		'<div align=center><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:center><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:center><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=justify><p>[foo]<p>bar</div><p>extra',
+		'<div align=justify><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:justify><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:justify><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=left><p>[foo]<p>bar</div><p>extra',
+		'<div align=left><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:left><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:left><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=right><p>[foo]<p>bar</div><p>extra',
+		'<div align=right><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:right><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:right><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=nonsense><p>[foo]</div><p>extra',
+		'<div style=text-align:inherit><p>[foo]</div><p>extra',
+		'<quasit align=center><p>[foo]</p></quasit><p>extra',
+	],
+	justifyleft: [
+		'foo[]bar<p>extra',
+		'<span>foo</span>{}<span>bar</span><p>extra',
+		'<span>foo[</span><span>]bar</span><p>extra',
+		'foo[bar]baz<p>extra',
+		'foo[bar<b>baz]qoz</b>quz<p>extra',
+		'<p>foo[]bar<p>extra',
+		'<p>foo[bar]baz<p>extra',
+		'<h1>foo[bar]baz</h1><p>extra',
+		'<pre>foo[bar]baz</pre><p>extra',
+		'<xmp>foo[bar]baz</xmp><p>extra',
+		'{<table><tr><td>foo<td>bar</table>}<p>extra',
+		'<center><p>[foo]<p>bar</center><p>extra',
+		'<center><p>[foo<p>bar]</center><p>extra',
+
+		'<div align=center><p>[foo]<p>bar</div><p>extra',
+		'<div align=center><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:center><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:center><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=justify><p>[foo]<p>bar</div><p>extra',
+		'<div align=justify><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:justify><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:justify><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=left><p>[foo]<p>bar</div><p>extra',
+		'<div align=left><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:left><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:left><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=right><p>[foo]<p>bar</div><p>extra',
+		'<div align=right><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:right><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:right><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=nonsense><p>[foo]</div><p>extra',
+		'<div style=text-align:inherit><p>[foo]</div><p>extra',
+		'<quasit align=center><p>[foo]</p></quasit><p>extra',
+	],
+	justifyright: [
+		'foo[]bar<p>extra',
+		'<span>foo</span>{}<span>bar</span><p>extra',
+		'<span>foo[</span><span>]bar</span><p>extra',
+		'foo[bar]baz<p>extra',
+		'foo[bar<b>baz]qoz</b>quz<p>extra',
+		'<p>foo[]bar<p>extra',
+		'<p>foo[bar]baz<p>extra',
+		'<h1>foo[bar]baz</h1><p>extra',
+		'<pre>foo[bar]baz</pre><p>extra',
+		'<xmp>foo[bar]baz</xmp><p>extra',
+		'{<table><tr><td>foo<td>bar</table>}<p>extra',
+		'<center><p>[foo]<p>bar</center><p>extra',
+		'<center><p>[foo<p>bar]</center><p>extra',
+
+		'<div align=center><p>[foo]<p>bar</div><p>extra',
+		'<div align=center><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:center><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:center><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=justify><p>[foo]<p>bar</div><p>extra',
+		'<div align=justify><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:justify><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:justify><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=left><p>[foo]<p>bar</div><p>extra',
+		'<div align=left><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:left><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:left><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=right><p>[foo]<p>bar</div><p>extra',
+		'<div align=right><p>[foo<p>bar}</div><p>extra',
+		'<div style=text-align:right><p>[foo]<p>bar</div><p>extra',
+		'<div style=text-align:right><p>[foo<p>bar]</div><p>extra',
+
+		'<div align=nonsense><p>[foo]</div><p>extra',
+		'<div style=text-align:inherit><p>[foo]</div><p>extra',
+		'<quasit align=center><p>[foo]</p></quasit><p>extra',
+	],
 	outdent: [
 		// These mimic existing indentation in various browsers, to see how
 		// they cope with outdenting various things.  This is spec, Gecko
@@ -1964,6 +2120,10 @@
 	"fontsize",
 	"forecolor",
 	"italic",
+	"justifycenter",
+	"justifyfull",
+	"justifyleft",
+	"justifyright",
 	"strikethrough",
 	"subscript",
 	"superscript",
--- a/editcommands.html	Thu Jun 02 12:43:16 2011 -0600
+++ b/editcommands.html	Thu Jun 02 14:34:46 2011 -0600
@@ -38,7 +38,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-31-may-2011>Work in Progress &mdash; Last Update 31 May 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-2-june-2011>Work in Progress &mdash; Last Update 2 June 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;[email protected]&gt;
@@ -104,13 +104,18 @@
    <li><a href=#block-formatting-a-node-list><span class=secno>7.4 </span>Block-formatting a node list</a></li>
    <li><a href=#outdenting-a-node><span class=secno>7.5 </span>Outdenting a node</a></li>
    <li><a href=#toggling-lists><span class=secno>7.6 </span>Toggling lists</a></li>
-   <li><a href=#the-formatblock-command><span class=secno>7.7 </span>The <code title="">formatBlock</code> command</a></li>
-   <li><a href=#the-indent-command><span class=secno>7.8 </span>The <code title="">indent</code> command</a></li>
-   <li><a href=#the-inserthorizontalrule-command><span class=secno>7.9 </span>The <code title="">insertHorizontalRule</code> command</a></li>
-   <li><a href=#the-insertorderedlist-command><span class=secno>7.10 </span>The <code title="">insertOrderedList</code> command</a></li>
-   <li><a href=#the-insertparagraph-command><span class=secno>7.11 </span>The <code title="">insertParagraph</code> command</a></li>
-   <li><a href=#the-insertunorderedlist-command><span class=secno>7.12 </span>The <code title="">insertUnorderedList</code> command</a></li>
-   <li><a href=#the-outdent-command><span class=secno>7.13 </span>The <code title="">outdent</code> command</a></ol></li>
+   <li><a href=#justifying-the-selection><span class=secno>7.7 </span>Justifying the selection</a></li>
+   <li><a href=#the-formatblock-command><span class=secno>7.8 </span>The <code title="">formatBlock</code> command</a></li>
+   <li><a href=#the-indent-command><span class=secno>7.9 </span>The <code title="">indent</code> command</a></li>
+   <li><a href=#the-inserthorizontalrule-command><span class=secno>7.10 </span>The <code title="">insertHorizontalRule</code> command</a></li>
+   <li><a href=#the-insertorderedlist-command><span class=secno>7.11 </span>The <code title="">insertOrderedList</code> command</a></li>
+   <li><a href=#the-insertparagraph-command><span class=secno>7.12 </span>The <code title="">insertParagraph</code> command</a></li>
+   <li><a href=#the-insertunorderedlist-command><span class=secno>7.13 </span>The <code title="">insertUnorderedList</code> command</a></li>
+   <li><a href=#the-justifycenter-command><span class=secno>7.14 </span>The <code title="">justifyCenter</code> command</a></li>
+   <li><a href=#the-justifyfull-command><span class=secno>7.15 </span>The <code title="">justifyFull</code> command</a></li>
+   <li><a href=#the-justifyleft-command><span class=secno>7.16 </span>The <code title="">justifyLeft</code> command</a></li>
+   <li><a href=#the-justifyright-command><span class=secno>7.17 </span>The <code title="">justifyRight</code> command</a></li>
+   <li><a href=#the-outdent-command><span class=secno>7.18 </span>The <code title="">outdent</code> command</a></ol></li>
  <li><a href=#miscellaneous-commands><span class=secno>8 </span>Miscellaneous commands</a>
   <ol>
    <li><a href=#the-stylewithcss-command><span class=secno>8.1 </span>The <code title="">styleWithCSS</code> command</a></li>
@@ -1106,14 +1111,11 @@
   There's code for these in WebKit, Source/WebCore/editing/EditorCommand.cpp,
   but I didn't see them mentioned elsewhere.  Some might be worth adding.
 
-Things I haven't looked at:
+Things I haven't looked at that multiple browsers implement:
 
 * copy, cut, paste: Needs attention to security.
 * delete, redo, undo: Needs review of the Google work on this; will probably be
   quite complicated.
-* justifyCenter, justifyFull, justifyLeft, justifyRight: These look important
-  and should be similar to work I've already done, so they're
-  next on my list.
 * selectAll, unselect: Should be easy, although they seem redundant to just
   calling methods of the Selection.
 -->
@@ -4058,7 +4060,123 @@
 </ol>
 
 
-<h3 id=the-formatblock-command><span class=secno>7.7 </span><dfn>The <code title="">formatBlock</code> command</dfn></h3>
+<h3 id=justifying-the-selection><span class=secno>7.7 </span>Justifying the selection</h3>
+<!--
+There are two basic ways it works: using the align attribute, and using CSS
+text-align.  IE9 and Opera 11.11 use only the align attribute, Chrome 13 dev
+uses only text-align, and Firefox 5.0a2 varies based on styleWithCSS.  The two
+ways produce entirely different results, which is a real problem, so I don't
+think Firefox's approach is tenable.  text-align is more valid, and for typical
+contenteditable cases it works the same.  But for cases where you have
+fixed-width blocks, like tables or just divs with a width set, it behaves
+differently, and in those cases the align attribute is more useful.
+-->
+
+<p>To <dfn id=justify-the-selection>justify the selection</dfn> to a string <var title="">alignment</var> (either
+"center", "justify", "left", or "right"):
+
+<p class=XXX>text-align doesn't behave as expected if there are descendant
+blocks with non-100% width, like tables.  The align attribute behaves a lot
+more nicely in such cases, but it's not valid.  Not clear what to do.  For now
+I've stuck with text-align, just because the cases where it misbehaves can't be
+created by any sequence of stock execCommand()s that I know of, but this needs
+more careful consideration.  Gecko in CSS mode seems to special-case tables,
+adding auto margins to the table element to get it to align correctly.
+
+<p class=XXX>Need to do something along the lines of pushing down values here
+(although no browser does).  In fact, it's very likely this can be rewritten in
+terms of the inline formatting command primitives, but it's not clear if it
+would be worth the added complexity.
+
+<ol>
+  <li><a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>, and let <var title="">new
+  range</var> be the result.
+
+  <li>Let <var title="">element list</var> be a list of all <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>s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new range</var> that either has an
+  attribute in the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#html-namespace>HTML namespace</a> whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attr-local-name title=concept-attr-local-name>local name</a> is "align", or 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 "text-align", or is a <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#center>center</a></code>.
+  <!-- No browser actually removes center, but it makes sense to do so. -->
+
+  <li>For each <var title="">element</var> in <var title="">element list</var>:
+
+  <ol>
+    <li>If <var title="">element</var> has an attribute in the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#html-namespace>HTML namespace</a> whose
+    <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attr-local-name title=concept-attr-local-name>local name</a> is "align", remove that attribute.
+
+    <li>Unset the CSS property "text-align" on <var title="">element</var>, if it's set
+    by 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.
+
+    <li>If <var title="">element</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><a href=http://www.whatwg.org/html/#center>center</a></code> with no attributes, remove it,
+    <a href=#preserving-its-descendants>preserving its descendants</a>.
+
+    <li>If <var title="">element</var> is a <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#center>center</a></code>
+    with one or more attributes, <a href=#set-the-tag-name>set the tag name</a> of
+    <var title="">element</var> to "div".
+  </ol>
+
+  <li><a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>, and let <var title="">new
+  range</var> be the result.
+  <!-- This could theoretically be necessary, like if it converted "<div
+  align=right>foo</div>bar" to "foo<br>bar".  Now we need to select "foo<br>",
+  nor just "foo". -->
+
+  <li>Let <var title="">node list</var> be a list of <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>, initially empty.
+
+  <li>For each <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> <var title="">node</var> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new range</var>,
+  append <var title="">node</var> to <var title="">node list</var> if the last member of
+  <var title="">node list</var> (if any) is not an <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of <var title="">node</var>;
+  <var title="">node</var> is <a href=#editable>editable</a>; and either <var title="">node</var> 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> and the CSS property "text-align" does not compute to
+  <var title="">alignment</var> on it, or it is not 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>, but its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>
+  is an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>, and the CSS property "text-align" does not compute to
+  <var title="">alignment</var> on its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+  <!-- Of tested browsers, only Chrome 13 dev seems to not apply the alignment
+  to nodes that are already aligned.  Even then, it does apply it if the
+  alignment is just inherited from the root. -->
+
+  <p class=XXX>What does this imply when text-align is "start" or "end"?  As
+  with lots of CSS stuff in this spec, needs to be clarified.
+
+  <li>While <var title="">node list</var> is not empty:
+
+  <ol>
+    <li>Let <var title="">sublist</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>Remove the first member of <var title="">node list</var> and append it to
+    <var title="">sublist</var>.
+
+    <li>While <var title="">node list</var> is not empty, and the first member of
+    <var title="">node list</var> is the <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> of the last member of
+    <var title="">sublist</var>, remove the first member of <var title="">node list</var> and
+    append it to <var title="">sublist</var>.
+
+    <li><a href=#wrap>Wrap</a> <var title="">sublist</var>.  <a href=#sibling-criteria>Sibling criteria</a>
+    match any <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 one or both of the following two attributes, and
+    no other attributes:
+
+    <ul>
+      <li>An <code class=external data-anolis-spec=html title=attr-div-align><a href=http://www.whatwg.org/html/#attr-div-align>align</a></code>
+      attribute whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attr-value title=concept-attr-value>value</a> is an <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#ascii-case-insensitive>ASCII
+      case-insensitive</a> match for <var title="">alignment</var>.
+
+      <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 which sets exactly one CSS property (including
+      unrecognized or invalid attributes), which is "text-align", which is set
+      to <var title="">alignment</var>.
+    </ul>
+
+    <a href=#new-parent-instructions>New parent instructions</a> are to call <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 <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>, then set its CSS property "text-align" to
+    <var title="">alignment</var>, and return the result.
+    <!-- As for inline formatting, I only ever create new elements, and don't
+    ever modify existing ones.  This doesn't match how any browser behaves
+    in this case, but for inline formatting it matches everyone but Gecko's CSS
+    mode. -->
+  </ol>
+</ol>
+
+
+<h3 id=the-formatblock-command><span class=secno>7.8 </span><dfn>The <code title="">formatBlock</code> command</dfn></h3>
 <!--
 Tested browser versions: IE9, Firefox 4.0, Chrome 13 dev, Opera 11.10.
 
@@ -4142,7 +4260,7 @@
 </ol>
 
 
-<h3 id=the-indent-command><span class=secno>7.8 </span><dfn>The <code title="">indent</code> command</dfn></h3>
+<h3 id=the-indent-command><span class=secno>7.9 </span><dfn>The <code title="">indent</code> command</dfn></h3>
 <!--
 IE9: Outputs <blockquote style="margin-right: 0px" dir="ltr">, or when
   surrounding RTL blocks, <blockquote style="margin-left: 0px" dir="rtl">.  The
@@ -4245,7 +4363,7 @@
 </ol>
 
 
-<h3 id=the-inserthorizontalrule-command><span class=secno>7.9 </span><dfn>The <code title="">insertHorizontalRule</code> command</dfn></h3>
+<h3 id=the-inserthorizontalrule-command><span class=secno>7.10 </span><dfn>The <code title="">insertHorizontalRule</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -4299,13 +4417,13 @@
 </ol>
 
 
-<h3 id=the-insertorderedlist-command><span class=secno>7.10 </span><dfn>The <code title="">insertOrderedList</code> command</dfn></h3>
+<h3 id=the-insertorderedlist-command><span class=secno>7.11 </span><dfn>The <code title="">insertOrderedList</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
 "ol".
 
 
-<h3 id=the-insertparagraph-command><span class=secno>7.11 </span><dfn>The <code title="">insertParagraph</code> command</dfn></h3>
+<h3 id=the-insertparagraph-command><span class=secno>7.12 </span><dfn>The <code title="">insertParagraph</code> command</dfn></h3>
 <!--
 There are three major behaviors here.  Firefox 5.0a2 behaves identically to
 execCommand("formatBlock", false, "p"), which is not really useful.  IE9
@@ -4523,13 +4641,37 @@
 </ol>
 
 
-<h3 id=the-insertunorderedlist-command><span class=secno>7.12 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
+<h3 id=the-insertunorderedlist-command><span class=secno>7.13 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
 "ul".
 
 
-<h3 id=the-outdent-command><span class=secno>7.13 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
+<h3 id=the-justifycenter-command><span class=secno>7.14 </span><dfn>The <code title="">justifyCenter</code> command</dfn></h3>
+
+<p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
+<var title="">alignment</var> "center".
+
+
+<h3 id=the-justifyfull-command><span class=secno>7.15 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
+
+<p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
+<var title="">alignment</var> "justify".
+
+
+<h3 id=the-justifyleft-command><span class=secno>7.16 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
+
+<p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
+<var title="">alignment</var> "left".
+
+
+<h3 id=the-justifyright-command><span class=secno>7.17 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
+
+<p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
+<var title="">alignment</var> "right".
+
+
+<h3 id=the-outdent-command><span class=secno>7.18 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
--- a/implementation.js	Thu Jun 02 12:43:16 2011 -0600
+++ b/implementation.js	Thu Jun 02 14:34:46 2011 -0600
@@ -355,6 +355,47 @@
 	return nodeList;
 }
 
+/**
+ * As above, but includes nodes with an ancestor that's already been returned.
+ */
+function collectAllContainedNodes(range, condition) {
+	if (typeof condition == "undefined") {
+		condition = function() { return true };
+	}
+	var node = range.startContainer;
+	if (node.hasChildNodes()
+	&& range.startOffset < node.childNodes.length) {
+		// A child is contained
+		node = node.childNodes[range.startOffset];
+	} else if (range.startOffset == getNodeLength(node)) {
+		// No descendant can be contained
+		node = nextNodeDescendants(node);
+	} else {
+		// No children; this node at least can't be contained
+		node = nextNode(node);
+	}
+
+	var stop = range.endContainer;
+	if (stop.hasChildNodes()
+	&& range.endOffset < stop.childNodes.length) {
+		// The node after the last contained node is a child
+		stop = stop.childNodes[range.endOffset];
+	} else {
+		// This node and/or some of its children might be contained
+		stop = nextNodeDescendants(node);
+	}
+
+	var nodeList = [];
+	while (isBefore(node, stop)) {
+		if (isContained(node, range)
+		&& condition(node)) {
+			nodeList.push(node);
+		}
+		node = nextNode(node);
+	}
+	return nodeList;
+}
+
 
 function parseSimpleColor(color) {
 	// This is stupid, but otherwise my automated tests will have places where
@@ -3451,6 +3492,22 @@
 		}
 		break;
 
+		case "justifycenter":
+		justifySelection("center");
+		break;
+
+		case "justifyfull":
+		justifySelection("justify");
+		break;
+
+		case "justifyleft":
+		justifySelection("left");
+		break;
+
+		case "justifyright":
+		justifySelection("right");
+		break;
+
 		case "outdent":
 		// "Let items be a list of all lis that are ancestor containers of the
 		// range's start and/or end node."
@@ -4303,6 +4360,141 @@
 	}
 }
 
+function justifySelection(alignment) {
+	// "Block-extend the active range, and let new range be the result."
+	var newRange = blockExtendRange(globalRange);
+
+	// "Let element list be a list of all editable Elements contained in new
+	// range that either has an attribute in the HTML namespace whose local
+	// name is "align", or has a style attribute that sets "text-align", or is
+	// a center."
+	var elementList = collectAllContainedNodes(newRange, function(node) {
+		return node.nodeType == Node.ELEMENT_NODE
+			&& isEditable(node)
+			// Ignoring namespaces here
+			&& (
+				node.hasAttribute("align")
+				|| node.style.textAlign != ""
+				|| isHtmlElement(node, "center")
+			);
+	});
+
+	// "For each element in element list:"
+	for (var i = 0; i < elementList.length; i++) {
+		var element = elementList[i];
+
+		// "If element has an attribute in the HTML namespace whose local name
+		// is "align", remove that attribute."
+		element.removeAttribute("align");
+
+		// "Unset the CSS property "text-align" on element, if it's set by a
+		// style attribute."
+		element.style.textAlign = "";
+		if (element.getAttribute("style") == "") {
+			element.removeAttribute("style");
+		}
+
+		// "If element is a div or center with no attributes, remove it,
+		// preserving its descendants."
+		if (isHtmlElement(element, ["div", "center"])
+		&& !element.attributes.length) {
+			removePreservingDescendants(element);
+		}
+
+		// "If element is a center with one or more attributes, set the tag
+		// name of element to "div"."
+		if (isHtmlElement(element, "center")
+		&& element.attributes.length) {
+			setTagName(element, "div");
+		}
+	}
+
+	// "Block-extend the active range, and let new range be the result."
+	newRange = blockExtendRange(globalRange);
+
+	// "Let node list be a list of nodes, initially empty."
+	var nodeList = [];
+
+	// "For each node node contained in new range, append node to node list if
+	// the last member of node list (if any) is not an ancestor of node; node
+	// is editable; and either node is an Element and the CSS property
+	// "text-align" does not compute to alignment on it, or it is not an
+	// Element, but its parent is an Element, and the CSS property "text-align"
+	// does not compute to alignment on its parent."
+	nodeList = collectContainedNodes(newRange, function(node) {
+		if (!isEditable(node)) {
+			return false;
+		}
+		// Gecko and WebKit have lots of fun here confusing us with
+		// vendor-specific values, and in Gecko's case "start".
+		var element = node.nodeType == Node.ELEMENT_NODE
+			? node
+			: node.parentNode;
+		if (!element || element.nodeType != Node.ELEMENT_NODE) {
+			return false;
+		}
+		var computedAlign = getComputedStyle(element).textAlign
+			.replace(/^-(moz|webkit)-/, "");
+		if (computedAlign == "auto" || computedAlign == "start") {
+			// Depends on directionality.  Note: this is a serious hack.
+			do {
+				var dir = element.dir.toLowerCase();
+				element = element.parentNode;
+			} while (element && element.nodeType == Node.ELEMENT_NODE && dir != "ltr" && dir != "rtl");
+			if (dir == "rtl") {
+				computedAlign = "right";
+			} else {
+				computedAlign = "left";
+			}
+		}
+		return computedAlign != alignment;
+	});
+
+	// "While node list is not empty:"
+	while (nodeList.length) {
+		// "Let sublist be a list of nodes, initially empty."
+		var sublist = [];
+
+		// "Remove the first member of node list and append it to sublist."
+		sublist.push(nodeList.shift());
+
+		// "While node list is not empty, and the first member of node list is
+		// the nextSibling of the last member of sublist, remove the first
+		// member of node list and append it to sublist."
+		while (nodeList.length
+		&& nodeList[0] == sublist[sublist.length - 1].nextSibling) {
+			sublist.push(nodeList.shift());
+		}
+
+		// "Wrap sublist. Sibling criteria match any div that has one or both
+		// of the following two attributes, and no other attributes:
+		//
+		//   * "An align attribute whose value is an ASCII case-insensitive
+		//     match for alignment.
+		//   * "A style attribute which sets exactly one CSS property
+		//     (including unrecognized or invalid attributes), which is
+		//     "text-align", which is set to alignment.
+		//
+		// "New parent instructions are to call createElement("div") on the
+		// context object, then set its CSS property "text-align" to alignment,
+		// and return the result."
+		wrap(sublist,
+			function(node) {
+				return isHtmlElement(node, "div")
+					&& div.attributes.every(function(attr) {
+						return (attr.name == "align" && attr.value.toLowerCase() == alignment)
+							|| (attr.name == "style" && node.style.length == 1 && node.style.textAlign == alignment);
+					});
+			},
+			function() {
+				var newParent = document.createElement("div");
+				newParent.setAttribute("style", "text-align: " + alignment);
+				return newParent;
+			}
+		);
+	}
+}
+
 // "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) {
--- a/source.html	Thu Jun 02 12:43:16 2011 -0600
+++ b/source.html	Thu Jun 02 14:34:46 2011 -0600
@@ -1076,14 +1076,11 @@
   There's code for these in WebKit, Source/WebCore/editing/EditorCommand.cpp,
   but I didn't see them mentioned elsewhere.  Some might be worth adding.
 
-Things I haven't looked at:
+Things I haven't looked at that multiple browsers implement:
 
 * copy, cut, paste: Needs attention to security.
 * delete, redo, undo: Needs review of the Google work on this; will probably be
   quite complicated.
-* justifyCenter, justifyFull, justifyLeft, justifyRight: These look important
-  and should be similar to work I've already done, so they're
-  next on my list.
 * selectAll, unselect: Should be easy, although they seem redundant to just
   calling methods of the Selection.
 -->
@@ -4080,6 +4077,124 @@
 </ol>
 
 
+<h3>Justifying the selection</h3>
+<!--
+There are two basic ways it works: using the align attribute, and using CSS
+text-align.  IE9 and Opera 11.11 use only the align attribute, Chrome 13 dev
+uses only text-align, and Firefox 5.0a2 varies based on styleWithCSS.  The two
+ways produce entirely different results, which is a real problem, so I don't
+think Firefox's approach is tenable.  text-align is more valid, and for typical
+contenteditable cases it works the same.  But for cases where you have
+fixed-width blocks, like tables or just divs with a width set, it behaves
+differently, and in those cases the align attribute is more useful.
+-->
+
+<p>To <dfn>justify the selection</dfn> to a string <var>alignment</var> (either
+"center", "justify", "left", or "right"):
+
+<p class=XXX>text-align doesn't behave as expected if there are descendant
+blocks with non-100% width, like tables.  The align attribute behaves a lot
+more nicely in such cases, but it's not valid.  Not clear what to do.  For now
+I've stuck with text-align, just because the cases where it misbehaves can't be
+created by any sequence of stock execCommand()s that I know of, but this needs
+more careful consideration.  Gecko in CSS mode seems to special-case tables,
+adding auto margins to the table element to get it to align correctly.
+
+<p class=XXX>Need to do something along the lines of pushing down values here
+(although no browser does).  In fact, it's very likely this can be rewritten in
+terms of the inline formatting command primitives, but it's not clear if it
+would be worth the added complexity.
+
+<ol>
+  <li><span>Block-extend</span> the <span>active range</span>, and let <var>new
+  range</var> be the result.
+
+  <li>Let <var>element list</var> be a list of all <span>editable</span>
+  [[element]]s [[contained]] in <var>new range</var> that either has an
+  attribute in the [[htmlnamespace]] whose [[attrlocalname]] is "align", or has
+  a [[style]] attribute that sets "text-align", or is a <code
+  data-anolis-spec=html>center</code>.
+  <!-- No browser actually removes center, but it makes sense to do so. -->
+
+  <li>For each <var>element</var> in <var>element list</var>:
+
+  <ol>
+    <li>If <var>element</var> has an attribute in the [[htmlnamespace]] whose
+    [[attrlocalname]] is "align", remove that attribute.
+
+    <li>Unset the CSS property "text-align" on <var>element</var>, if it's set
+    by a [[style]] attribute.
+
+    <li>If <var>element</var> is a [[div]] or <code
+    data-anolis-spec=html>center</code> with no attributes, remove it,
+    <span>preserving its descendants</span>.
+
+    <li>If <var>element</var> is a <code data-anolis-spec=html>center</code>
+    with one or more attributes, <span>set the tag name</span> of
+    <var>element</var> to "div".
+  </ol>
+
+  <li><span>Block-extend</span> the <span>active range</span>, and let <var>new
+  range</var> be the result.
+  <!-- This could theoretically be necessary, like if it converted "<div
+  align=right>foo</div>bar" to "foo<br>bar".  Now we need to select "foo<br>",
+  nor just "foo". -->
+
+  <li>Let <var>node list</var> be a list of [[nodes]], initially empty.
+
+  <li>For each [[node]] <var>node</var> [[contained]] in <var>new range</var>,
+  append <var>node</var> to <var>node list</var> if the last member of
+  <var>node list</var> (if any) is not an [[ancestor]] of <var>node</var>;
+  <var>node</var> is <span>editable</span>; and either <var>node</var> is an
+  [[element]] and the CSS property "text-align" does not compute to
+  <var>alignment</var> on it, or it is not an [[element]], but its [[parent]]
+  is an [[element]], and the CSS property "text-align" does not compute to
+  <var>alignment</var> on its [[parent]].
+  <!-- Of tested browsers, only Chrome 13 dev seems to not apply the alignment
+  to nodes that are already aligned.  Even then, it does apply it if the
+  alignment is just inherited from the root. -->
+
+  <p class=XXX>What does this imply when text-align is "start" or "end"?  As
+  with lots of CSS stuff in this spec, needs to be clarified.
+
+  <li>While <var>node list</var> is not empty:
+
+  <ol>
+    <li>Let <var>sublist</var> be a list of [[nodes]], initially empty.
+
+    <li>Remove the first member of <var>node list</var> and append it to
+    <var>sublist</var>.
+
+    <li>While <var>node list</var> is not empty, and the first member of
+    <var>node list</var> is the [[nextsibling]] of the last member of
+    <var>sublist</var>, remove the first member of <var>node list</var> and
+    append it to <var>sublist</var>.
+
+    <li><span>Wrap</span> <var>sublist</var>.  <span>Sibling criteria</span>
+    match any [[div]] that has one or both of the following two attributes, and
+    no other attributes:
+
+    <ul>
+      <li>An <code data-anolis-spec=html title=attr-div-align>align</code>
+      attribute whose [[attrvalue]] is an <span data-anolis-spec=domcore>ASCII
+      case-insensitive</span> match for <var>alignment</var>.
+
+      <li>A [[style]] attribute which sets exactly one CSS property (including
+      unrecognized or invalid attributes), which is "text-align", which is set
+      to <var>alignment</var>.
+    </ul>
+
+    <span>New parent instructions</span> are to call [[createelement|"div"]] on
+    the [[contextobject]], then set its CSS property "text-align" to
+    <var>alignment</var>, and return the result.
+    <!-- As for inline formatting, I only ever create new elements, and don't
+    ever modify existing ones.  This doesn't match how any browser behaves
+    in this case, but for inline formatting it matches everyone but Gecko's CSS
+    mode. -->
+  </ol>
+</ol>
+
+
 <h3><dfn>The <code title>formatBlock</code> command</dfn></h3>
 <!--
 Tested browser versions: IE9, Firefox 4.0, Chrome 13 dev, Opera 11.10.
@@ -4564,6 +4679,30 @@
 "ul".
 
 
+<h3><dfn>The <code title>justifyCenter</code> command</dfn></h3>
+
+<p><span>Action</span>: <span>Justify the selection</span> with
+<var>alignment</var> "center".
+
+
+<h3><dfn>The <code title>justifyFull</code> command</dfn></h3>
+
+<p><span>Action</span>: <span>Justify the selection</span> with
+<var>alignment</var> "justify".
+
+
+<h3><dfn>The <code title>justifyLeft</code> command</dfn></h3>
+
+<p><span>Action</span>: <span>Justify the selection</span> with
+<var>alignment</var> "left".
+
+
+<h3><dfn>The <code title>justifyRight</code> command</dfn></h3>
+
+<p><span>Action</span>: <span>Justify the selection</span> with
+<var>alignment</var> "right".
+
+
 <h3><dfn>The <code title>outdent</code> command</dfn></h3>
 
 <p><span>Action</span>: