Improve justify* indeterm/state/value definitions
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Wed, 29 Jun 2011 12:42:49 -0600
changeset 346 5916e1f46d55
parent 345 2be533157a92
child 347 af1f237ace1c
Improve justify* indeterm/state/value definitions

Now they make more sense.
editcommands.html
implementation.js
source.html
tests.js
--- a/editcommands.html	Wed Jun 29 11:40:29 2011 -0600
+++ b/editcommands.html	Wed Jun 29 12:42:49 2011 -0600
@@ -3502,55 +3502,6 @@
   assume something CSS 2.1-ish.
 </ol>
 
-<p>The <dfn id="selection's-alignment-value">selection's alignment value</dfn> is returned by the following
-algorithm:
-<!--
-IE9 throws exceptions in almost every case when querying the state of justify*,
-and Opera 11.11 returns false in every case except some seemingly random crazy
-ones.
-
-Firefox 6.0a2 returns true for the state of justify* if anything in the range
-has the right alignment, not if everything does.  This isn't consistent with
-how state works for the inline commands, nor with WebKit.
-
-Chrome 14 dev counts text-align on inline elements, which is wrong, because the
-property has no effect.  It also counts it on non-editable elements, which is
-wrong, because then the state for justify* wouldn't necessarily be true after
-executing it.  (Chrome actually does align the non-editable elements, but
-that's just a bug.)  Chrome further returns false for justify* if the
-justification is just the default inherited justification, e.g., left for LTR.
-This doesn't seem to make sense either.
-
-It's not actually clear how much sense state makes here.  Most commands that
-have a state that's not always false work differently depending on what the
-state is, generally because they toggle something (bold, lists, etc.).  The
-justify commands behave the same regardless of state.  Still, I'll follow
-Gecko/WebKit in supporting the command.
--->
-<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="">node list</var> be a list of all <a href=#editable>editable</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>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>.
-
-  <li>If <var title="">node list</var> is empty, return "none".
-
-  <li>Let <var title="">state</var> be "none".
-
-  <li>For each <var title="">node</var> in <var title="">node list</var>:
-
-  <ol>
-    <li>If <var title="">state</var> is "none", set <var title="">state</var> to
-    <var title="">node</var>'s <a href=#alignment-value>alignment value</a>.
-
-    <li>If <var title="">state</var> is different from <var title="">node</var>'s <a href=#alignment-value>alignment
-    value</a>, return "none".
-  </ol>
-
-  <li>Return <var title="">state</var>.
-</ol>
-
 
 <h3 id=allowed-children><span class=secno>8.3 </span>Allowed children</h3>
 
@@ -6428,17 +6379,64 @@
 <var title="">alignment</var> "center".
 
 <p><a href=#indeterminate>Indeterminate</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active
-range</a>.  Return true if among <a href=#editable>editable</a> nodes <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a>
-in the result, at least one has <a href=#alignment-value>alignment value</a> "center" and at
+range</a>.  Return true if among <a href=#visible-node title="visible node">visible</a>
+<a href=#editable>editable</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>nodes</a> that are <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the result and 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>, at least one has <a href=#alignment-value>alignment value</a> "center" and at
 least one does not.  Otherwise return false.
-<!-- This roughly matches Chrome 14 dev, although not exactly.  Firefox 6.0a2
-always returns false. -->
-
-<p><a href=#state>State</a>: True if the <a href="#selection's-alignment-value">selection's alignment value</a> is
-"center", otherwise false.
-
-<p><a href=#value>Value</a>: The <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>'s
-<a href=#alignment-value>alignment value</a>.
+<!--
+This roughly matches Chrome 14 dev, although not exactly.  Firefox 6.0a2 always
+returns false.
+
+As a general rule, ignoring nodes with children saves us from treating <div
+align=left><div align=center>foo</div></div> as though it's indeterminate.
+Chrome 14 dev seems to only pay attention to text nodes, instead, or something
+like that.  At any rate, it fails on images.  Firefox 6.0a2 (for state and
+value) gets tripped up by examples like the one given.
+
+If we ever support centering of tables and similar, we'd want to pay attention
+even to some nodes that do have children.
+-->
+
+<p><a href=#state>State</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>.
+Return true if there is at least one <a href=#visible-node title="visible node">visible</a>
+<a href=#editable>editable</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> that is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the result and has 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>, and all such <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> have <a href=#alignment-value>alignment value</a>
+"center".  Otherwise return false.
+<!--
+IE9 throws exceptions in almost every case when querying the state of justify*,
+and Opera 11.11 returns false in every case except some seemingly random crazy
+ones.
+
+Firefox 6.0a2 returns true for the state of justify* if anything in the range
+has the right alignment, not if everything does.  This isn't consistent with
+how state works for the inline commands, nor with WebKit.
+
+Chrome 14 dev counts text-align on inline elements, which is wrong, because the
+property has no effect.  It also counts it on non-editable elements, which is
+wrong, because then the state for justify* wouldn't necessarily be true after
+executing it.  (Chrome actually does align the non-editable elements, but
+that's just a bug.)  Chrome further returns false for justify* if the
+justification is just the default inherited justification, e.g., left for LTR.
+This doesn't seem to make sense either.
+
+State is kind of redundant here, because it's true if and only if indeterminate
+is false and the value is equal to the desired value.  However, I'll support it
+anyway, since Gecko/WebKit do.
+-->
+
+<p><a href=#value>Value</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>,
+and return the <a href=#alignment-value>alignment value</a> of the first <a href=#visible-node title="visible
+node">visible</a> <a href=#editable>editable</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> that is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in
+the result and has 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>.  If there is no such <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>, return
+"left".
+<!--
+Not bidi-safe, but it's a pretty marginal corner case.  Firefox 6.0a2 behaves
+weirdly here: it keys off the start node of the active range, even if that's
+not contained.  Thus {<div align=center>foo</div>} has value "left" and
+indeterminate false, which would suggest that the whole selection is aligned
+left, but that's not the case.  Chrome 14 dev returns the state cast to a
+string, as usual.  Opera 11.11 always returns the empty string.
+-->
 
 
 <h3 id=the-justifyfull-command><span class=secno>8.22 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
@@ -6447,15 +6445,22 @@
 <var title="">alignment</var> "justify".
 
 <p><a href=#indeterminate>Indeterminate</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active
-range</a>.  Return true if among <a href=#editable>editable</a> nodes <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a>
-in the result, at least one has <a href=#alignment-value>alignment value</a> "justify" and at
+range</a>.  Return true if among <a href=#visible-node title="visible node">visible</a>
+<a href=#editable>editable</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>nodes</a> that are <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the result and 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>, at least one has <a href=#alignment-value>alignment value</a> "justify" and at
 least one does not.  Otherwise return false.
 
-<p><a href=#state>State</a>: True if the <a href="#selection's-alignment-value">selection's alignment value</a> is
-"justify", otherwise false.
-
-<p><a href=#value>Value</a>: The <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>'s
-<a href=#alignment-value>alignment value</a>.
+<p><a href=#state>State</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>.
+Return true if there is at least one <a href=#visible-node title="visible node">visible</a>
+<a href=#editable>editable</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> that is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the result and has 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>, and all such <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> have <a href=#alignment-value>alignment value</a>
+"justify".  Otherwise return false.
+
+<p><a href=#value>Value</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>,
+and return the <a href=#alignment-value>alignment value</a> of the first <a href=#visible-node title="visible
+node">visible</a> <a href=#editable>editable</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> that is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in
+the result and has 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>.  If there is no such <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>, return
+"left".
 
 
 <h3 id=the-justifyleft-command><span class=secno>8.23 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
@@ -6464,15 +6469,22 @@
 <var title="">alignment</var> "left".
 
 <p><a href=#indeterminate>Indeterminate</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active
-range</a>.  Return true if among <a href=#editable>editable</a> nodes <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a>
-in the result, at least one has <a href=#alignment-value>alignment value</a> "left" and at
+range</a>.  Return true if among <a href=#visible-node title="visible node">visible</a>
+<a href=#editable>editable</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>nodes</a> that are <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the result and 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>, at least one has <a href=#alignment-value>alignment value</a> "left" and at
 least one does not.  Otherwise return false.
 
-<p><a href=#state>State</a>: True if the <a href="#selection's-alignment-value">selection's alignment value</a> is
-"left", otherwise false.
-
-<p><a href=#value>Value</a>: The <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>'s
-<a href=#alignment-value>alignment value</a>.
+<p><a href=#state>State</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>.
+Return true if there is at least one <a href=#visible-node title="visible node">visible</a>
+<a href=#editable>editable</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> that is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the result and has 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>, and all such <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> have <a href=#alignment-value>alignment value</a> "left".
+Otherwise return false.
+
+<p><a href=#value>Value</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>,
+and return the <a href=#alignment-value>alignment value</a> of the first <a href=#visible-node title="visible
+node">visible</a> <a href=#editable>editable</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> that is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in
+the result and has 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>.  If there is no such <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>, return
+"left".
 
 
 <h3 id=the-justifyright-command><span class=secno>8.24 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
@@ -6481,15 +6493,22 @@
 <var title="">alignment</var> "right".
 
 <p><a href=#indeterminate>Indeterminate</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active
-range</a>.  Return true if among <a href=#editable>editable</a> nodes <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a>
-in the result, at least one has <a href=#alignment-value>alignment value</a> "right" and at
+range</a>.  Return true if among <a href=#visible-node title="visible node">visible</a>
+<a href=#editable>editable</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>nodes</a> that are <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the result and 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>, at least one has <a href=#alignment-value>alignment value</a> "right" and at
 least one does not.  Otherwise return false.
 
-<p><a href=#state>State</a>: True if the <a href="#selection's-alignment-value">selection's alignment value</a> is
-"right", otherwise false.
-
-<p><a href=#value>Value</a>: The <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>'s
-<a href=#alignment-value>alignment value</a>.
+<p><a href=#state>State</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>.
+Return true if there is at least one <a href=#visible-node title="visible node">visible</a>
+<a href=#editable>editable</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> that is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the result and has 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>, and all such <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> have <a href=#alignment-value>alignment value</a> "right".
+Otherwise return false.
+
+<p><a href=#value>Value</a>: <a href=#block-extend>Block-extend</a> the <a href=#active-range>active range</a>,
+and return the <a href=#alignment-value>alignment value</a> of the first <a href=#visible-node title="visible
+node">visible</a> <a href=#editable>editable</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> that is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in
+the result and has 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>.  If there is no such <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>, return
+"left".
 
 
 <h3 id=the-outdent-command><span class=secno>8.25 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
--- a/implementation.js	Wed Jun 29 11:40:29 2011 -0600
+++ b/implementation.js	Wed Jun 29 12:42:49 2011 -0600
@@ -3874,41 +3874,6 @@
 	// computed value of node's "text-align" property."
 	return getRealTextAlign(node);
 }
-
-function getSelectionAlignmentValue() {
-	// "Block-extend the active range, and let new range be the result."
-	var newRange = blockExtendRange(getActiveRange());
-
-	// "Let node list be a list of all editable nodes contained in new
-	// range."
-	var nodeList = collectAllContainedNodes(newRange, isEditable);
-
-	// "If node list is empty, return "none"."
-	if (nodeList.length == 0) {
-		return "none";
-	}
-
-	// "Let state be "none"."
-	var state = "none";
-
-	// "For each node in node list:"
-	for (var i = 0; i < nodeList.length; i++) {
-		var node = nodeList[i];
-
-		// "If state is "none", set state to node's alignment value."
-		if (state == "none") {
-			state = getAlignmentValue(node);
-		}
-
-		// "If state is different from node's alignment value, return "none"."
-		if (state != getAlignmentValue(node)) {
-			return "none";
-		}
-	}
-
-	// "Return state."
-	return state;
-}
 //@}
 
 ///// Allowed children /////
@@ -6495,17 +6460,38 @@
 	// "Justify the selection with alignment "center"."
 	action: function() { justifySelection("center") },
 	indeterm: function() {
-		// "Block-extend the active range. Return true if among editable nodes
-		// contained in the result, at least one has alignment value "center"
-		// and at least one does not. Otherwise return false."
-		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), isEditable);
+		// "Block-extend the active range. Return true if among visible
+		// editable nodes that are contained in the result and have no
+		// children, at least one has alignment value "center" and at least one
+		// does not. Otherwise return false."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
 		return nodes.some(function(node) { return getAlignmentValue(node) == "center" })
 			&& nodes.some(function(node) { return getAlignmentValue(node) != "center" });
+	}, state: function() {
+		// "Block-extend the active range. Return true if there is at least one
+		// visible editable node that is contained in the result and has no
+		// children, and all such nodes have alignment value "center".
+		// Otherwise return false."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
+		return nodes.length
+			&& nodes.every(function(node) { return getAlignmentValue(node) == "center" });
+	}, value: function() {
+		// "Block-extend the active range, and return the alignment value of
+		// the first visible editable node that is contained in the result and
+		// has no children. If there is no such node, return "left"."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
+		if (nodes.length) {
+			return getAlignmentValue(nodes[0]);
+		} else {
+			return "left";
+		}
 	},
-	// "True if the selection's alignment value is "center", otherwise false."
-	state: function() { return getSelectionAlignmentValue() == "center" },
-	// "The active range's start node's alignment value."
-	value: function() { return getAlignmentValue(getActiveRange().startContainer) },
 };
 //@}
 
@@ -6515,17 +6501,38 @@
 	// "Justify the selection with alignment "justify"."
 	action: function() { justifySelection("justify") },
 	indeterm: function() {
-		// "Block-extend the active range. Return true if among editable nodes
-		// contained in the result, at least one has alignment value "justify"
-		// and at least one does not. Otherwise return false."
-		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), isEditable);
+		// "Block-extend the active range. Return true if among visible
+		// editable nodes that are contained in the result and have no
+		// children, at least one has alignment value "justify" and at least
+		// one does not. Otherwise return false."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
 		return nodes.some(function(node) { return getAlignmentValue(node) == "justify" })
 			&& nodes.some(function(node) { return getAlignmentValue(node) != "justify" });
+	}, state: function() {
+		// "Block-extend the active range. Return true if there is at least one
+		// visible editable node that is contained in the result and has no
+		// children, and all such nodes have alignment value "justify".
+		// Otherwise return false."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
+		return nodes.length
+			&& nodes.every(function(node) { return getAlignmentValue(node) == "justify" });
+	}, value: function() {
+		// "Block-extend the active range, and return the alignment value of
+		// the first visible editable node that is contained in the result and
+		// has no children. If there is no such node, return "left"."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
+		if (nodes.length) {
+			return getAlignmentValue(nodes[0]);
+		} else {
+			return "left";
+		}
 	},
-	// "True if the selection's alignment value is "justify", otherwise false."
-	state: function() { return getSelectionAlignmentValue() == "justify" },
-	// "The active range's start node's alignment value."
-	value: function() { return getAlignmentValue(getActiveRange().startContainer) },
 };
 //@}
 
@@ -6535,17 +6542,38 @@
 	// "Justify the selection with alignment "left"."
 	action: function() { justifySelection("left") },
 	indeterm: function() {
-		// "Block-extend the active range. Return true if among editable nodes
-		// contained in the result, at least one has alignment value "left"
-		// and at least one does not. Otherwise return false."
-		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), isEditable);
+		// "Block-extend the active range. Return true if among visible
+		// editable nodes that are contained in the result and have no
+		// children, at least one has alignment value "left" and at least one
+		// does not. Otherwise return false."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
 		return nodes.some(function(node) { return getAlignmentValue(node) == "left" })
 			&& nodes.some(function(node) { return getAlignmentValue(node) != "left" });
+	}, state: function() {
+		// "Block-extend the active range. Return true if there is at least one
+		// visible editable node that is contained in the result and has no
+		// children, and all such nodes have alignment value "left".  Otherwise
+		// return false."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
+		return nodes.length
+			&& nodes.every(function(node) { return getAlignmentValue(node) == "left" });
+	}, value: function() {
+		// "Block-extend the active range, and return the alignment value of
+		// the first visible editable node that is contained in the result and
+		// has no children. If there is no such node, return "left"."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
+		if (nodes.length) {
+			return getAlignmentValue(nodes[0]);
+		} else {
+			return "left";
+		}
 	},
-	// "True if the selection's alignment value is "left", otherwise false."
-	state: function() { return getSelectionAlignmentValue() == "left" },
-	// "The active range's start node's alignment value."
-	value: function() { return getAlignmentValue(getActiveRange().startContainer) },
 };
 //@}
 
@@ -6555,17 +6583,38 @@
 	// "Justify the selection with alignment "right"."
 	action: function() { justifySelection("right") },
 	indeterm: function() {
-		// "Block-extend the active range. Return true if among editable nodes
-		// contained in the result, at least one has alignment value "right"
-		// and at least one does not. Otherwise return false."
-		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), isEditable);
+		// "Block-extend the active range. Return true if among visible
+		// editable nodes that are contained in the result and have no
+		// children, at least one has alignment value "right" and at least one
+		// does not. Otherwise return false."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
 		return nodes.some(function(node) { return getAlignmentValue(node) == "right" })
 			&& nodes.some(function(node) { return getAlignmentValue(node) != "right" });
+	}, state: function() {
+		// "Block-extend the active range. Return true if there is at least one
+		// visible editable node that is contained in the result and has no
+		// children, and all such nodes have alignment value "right".
+		// Otherwise return false."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
+		return nodes.length
+			&& nodes.every(function(node) { return getAlignmentValue(node) == "right" });
+	}, value: function() {
+		// "Block-extend the active range, and return the alignment value of
+		// the first visible editable node that is contained in the result and
+		// has no children. If there is no such node, return "left"."
+		var nodes = collectAllContainedNodes(blockExtendRange(getActiveRange()), function(node) {
+			return isEditable(node) && isVisibleNode(node) && !node.hasChildNodes();
+		});
+		if (nodes.length) {
+			return getAlignmentValue(nodes[0]);
+		} else {
+			return "left";
+		}
 	},
-	// "True if the selection's alignment value is "right", otherwise false."
-	state: function() { return getSelectionAlignmentValue() == "right" },
-	// "The active range's start node's alignment value."
-	value: function() { return getAlignmentValue(getActiveRange().startContainer) },
 };
 //@}
 
--- a/source.html	Wed Jun 29 11:40:29 2011 -0600
+++ b/source.html	Wed Jun 29 12:42:49 2011 -0600
@@ -3492,55 +3492,6 @@
   <p class=XXX>What to do for "start" "auto" etc. currently undefined.  We
   assume something CSS 2.1-ish.
 </ol>
-
-<p>The <dfn>selection's alignment value</dfn> is returned by the following
-algorithm:
-<!--
-IE9 throws exceptions in almost every case when querying the state of justify*,
-and Opera 11.11 returns false in every case except some seemingly random crazy
-ones.
-
-Firefox 6.0a2 returns true for the state of justify* if anything in the range
-has the right alignment, not if everything does.  This isn't consistent with
-how state works for the inline commands, nor with WebKit.
-
-Chrome 14 dev counts text-align on inline elements, which is wrong, because the
-property has no effect.  It also counts it on non-editable elements, which is
-wrong, because then the state for justify* wouldn't necessarily be true after
-executing it.  (Chrome actually does align the non-editable elements, but
-that's just a bug.)  Chrome further returns false for justify* if the
-justification is just the default inherited justification, e.g., left for LTR.
-This doesn't seem to make sense either.
-
-It's not actually clear how much sense state makes here.  Most commands that
-have a state that's not always false work differently depending on what the
-state is, generally because they toggle something (bold, lists, etc.).  The
-justify commands behave the same regardless of state.  Still, I'll follow
-Gecko/WebKit in supporting the command.
--->
-<ol>
-  <li><span>Block-extend</span> the <span>active range</span>, and let <var>new
-  range</var> be the result.
-
-  <li>Let <var>node list</var> be a list of all <span>editable</span> [[nodes]]
-  [[contained]] in <var>new range</var>.
-
-  <li>If <var>node list</var> is empty, return "none".
-
-  <li>Let <var>state</var> be "none".
-
-  <li>For each <var>node</var> in <var>node list</var>:
-
-  <ol>
-    <li>If <var>state</var> is "none", set <var>state</var> to
-    <var>node</var>'s <span>alignment value</span>.
-
-    <li>If <var>state</var> is different from <var>node</var>'s <span>alignment
-    value</span>, return "none".
-  </ol>
-
-  <li>Return <var>state</var>.
-</ol>
 <!-- @} -->
 
 <h3>Allowed children</h3>
@@ -6454,17 +6405,64 @@
 <var>alignment</var> "center".
 
 <p><span>Indeterminate</span>: <span>Block-extend</span> the <span>active
-range</span>.  Return true if among <span>editable</span> nodes [[contained]]
-in the result, at least one has <span>alignment value</span> "center" and at
+range</span>.  Return true if among <span title="visible node">visible</span>
+<span>editable</span> [[nodes]] that are [[contained]] in the result and have
+no [[children]], at least one has <span>alignment value</span> "center" and at
 least one does not.  Otherwise return false.
-<!-- This roughly matches Chrome 14 dev, although not exactly.  Firefox 6.0a2
-always returns false. -->
-
-<p><span>State</span>: True if the <span>selection's alignment value</span> is
-"center", otherwise false.
-
-<p><span>Value</span>: The <span>active range</span>'s [[startnode]]'s
-<span>alignment value</span>.
+<!--
+This roughly matches Chrome 14 dev, although not exactly.  Firefox 6.0a2 always
+returns false.
+
+As a general rule, ignoring nodes with children saves us from treating <div
+align=left><div align=center>foo</div></div> as though it's indeterminate.
+Chrome 14 dev seems to only pay attention to text nodes, instead, or something
+like that.  At any rate, it fails on images.  Firefox 6.0a2 (for state and
+value) gets tripped up by examples like the one given.
+
+If we ever support centering of tables and similar, we'd want to pay attention
+even to some nodes that do have children.
+-->
+
+<p><span>State</span>: <span>Block-extend</span> the <span>active range</span>.
+Return true if there is at least one <span title="visible node">visible</span>
+<span>editable</span> [[node]] that is [[contained]] in the result and has no
+[[children]], and all such [[nodes]] have <span>alignment value</span>
+"center".  Otherwise return false.
+<!--
+IE9 throws exceptions in almost every case when querying the state of justify*,
+and Opera 11.11 returns false in every case except some seemingly random crazy
+ones.
+
+Firefox 6.0a2 returns true for the state of justify* if anything in the range
+has the right alignment, not if everything does.  This isn't consistent with
+how state works for the inline commands, nor with WebKit.
+
+Chrome 14 dev counts text-align on inline elements, which is wrong, because the
+property has no effect.  It also counts it on non-editable elements, which is
+wrong, because then the state for justify* wouldn't necessarily be true after
+executing it.  (Chrome actually does align the non-editable elements, but
+that's just a bug.)  Chrome further returns false for justify* if the
+justification is just the default inherited justification, e.g., left for LTR.
+This doesn't seem to make sense either.
+
+State is kind of redundant here, because it's true if and only if indeterminate
+is false and the value is equal to the desired value.  However, I'll support it
+anyway, since Gecko/WebKit do.
+-->
+
+<p><span>Value</span>: <span>Block-extend</span> the <span>active range</span>,
+and return the <span>alignment value</span> of the first <span title="visible
+node">visible</span> <span>editable</span> [[node]] that is [[contained]] in
+the result and has no [[children]].  If there is no such [[node]], return
+"left".
+<!--
+Not bidi-safe, but it's a pretty marginal corner case.  Firefox 6.0a2 behaves
+weirdly here: it keys off the start node of the active range, even if that's
+not contained.  Thus {<div align=center>foo</div>} has value "left" and
+indeterminate false, which would suggest that the whole selection is aligned
+left, but that's not the case.  Chrome 14 dev returns the state cast to a
+string, as usual.  Opera 11.11 always returns the empty string.
+-->
 <!-- @} -->
 
 <h3><dfn>The <code title>justifyFull</code> command</dfn></h3>
@@ -6473,15 +6471,22 @@
 <var>alignment</var> "justify".
 
 <p><span>Indeterminate</span>: <span>Block-extend</span> the <span>active
-range</span>.  Return true if among <span>editable</span> nodes [[contained]]
-in the result, at least one has <span>alignment value</span> "justify" and at
+range</span>.  Return true if among <span title="visible node">visible</span>
+<span>editable</span> [[nodes]] that are [[contained]] in the result and have
+no [[children]], at least one has <span>alignment value</span> "justify" and at
 least one does not.  Otherwise return false.
 
-<p><span>State</span>: True if the <span>selection's alignment value</span> is
-"justify", otherwise false.
-
-<p><span>Value</span>: The <span>active range</span>'s [[startnode]]'s
-<span>alignment value</span>.
+<p><span>State</span>: <span>Block-extend</span> the <span>active range</span>.
+Return true if there is at least one <span title="visible node">visible</span>
+<span>editable</span> [[node]] that is [[contained]] in the result and has no
+[[children]], and all such [[nodes]] have <span>alignment value</span>
+"justify".  Otherwise return false.
+
+<p><span>Value</span>: <span>Block-extend</span> the <span>active range</span>,
+and return the <span>alignment value</span> of the first <span title="visible
+node">visible</span> <span>editable</span> [[node]] that is [[contained]] in
+the result and has no [[children]].  If there is no such [[node]], return
+"left".
 <!-- @} -->
 
 <h3><dfn>The <code title>justifyLeft</code> command</dfn></h3>
@@ -6490,15 +6495,22 @@
 <var>alignment</var> "left".
 
 <p><span>Indeterminate</span>: <span>Block-extend</span> the <span>active
-range</span>.  Return true if among <span>editable</span> nodes [[contained]]
-in the result, at least one has <span>alignment value</span> "left" and at
+range</span>.  Return true if among <span title="visible node">visible</span>
+<span>editable</span> [[nodes]] that are [[contained]] in the result and have
+no [[children]], at least one has <span>alignment value</span> "left" and at
 least one does not.  Otherwise return false.
 
-<p><span>State</span>: True if the <span>selection's alignment value</span> is
-"left", otherwise false.
-
-<p><span>Value</span>: The <span>active range</span>'s [[startnode]]'s
-<span>alignment value</span>.
+<p><span>State</span>: <span>Block-extend</span> the <span>active range</span>.
+Return true if there is at least one <span title="visible node">visible</span>
+<span>editable</span> [[node]] that is [[contained]] in the result and has no
+[[children]], and all such [[nodes]] have <span>alignment value</span> "left".
+Otherwise return false.
+
+<p><span>Value</span>: <span>Block-extend</span> the <span>active range</span>,
+and return the <span>alignment value</span> of the first <span title="visible
+node">visible</span> <span>editable</span> [[node]] that is [[contained]] in
+the result and has no [[children]].  If there is no such [[node]], return
+"left".
 <!-- @} -->
 
 <h3><dfn>The <code title>justifyRight</code> command</dfn></h3>
@@ -6507,15 +6519,22 @@
 <var>alignment</var> "right".
 
 <p><span>Indeterminate</span>: <span>Block-extend</span> the <span>active
-range</span>.  Return true if among <span>editable</span> nodes [[contained]]
-in the result, at least one has <span>alignment value</span> "right" and at
+range</span>.  Return true if among <span title="visible node">visible</span>
+<span>editable</span> [[nodes]] that are [[contained]] in the result and have
+no [[children]], at least one has <span>alignment value</span> "right" and at
 least one does not.  Otherwise return false.
 
-<p><span>State</span>: True if the <span>selection's alignment value</span> is
-"right", otherwise false.
-
-<p><span>Value</span>: The <span>active range</span>'s [[startnode]]'s
-<span>alignment value</span>.
+<p><span>State</span>: <span>Block-extend</span> the <span>active range</span>.
+Return true if there is at least one <span title="visible node">visible</span>
+<span>editable</span> [[node]] that is [[contained]] in the result and has no
+[[children]], and all such [[nodes]] have <span>alignment value</span> "right".
+Otherwise return false.
+
+<p><span>Value</span>: <span>Block-extend</span> the <span>active range</span>,
+and return the <span>alignment value</span> of the first <span title="visible
+node">visible</span> <span>editable</span> [[node]] that is [[contained]] in
+the result and has no [[children]].  If there is no such [[node]], return
+"left".
 <!-- @} -->
 
 <h3><dfn>The <code title>outdent</code> command</dfn></h3>
--- a/tests.js	Wed Jun 29 11:40:29 2011 -0600
+++ b/tests.js	Wed Jun 29 12:42:49 2011 -0600
@@ -2412,6 +2412,8 @@
 		'<div align=left>{<div align=center>foo</div>bar}</div>',
 		'<div align=center>{<div align=left>foo</div><img src=/img/lion.svg>}</div>',
 		'<div align=left>{<div align=center>foo</div><img src=/img/lion.svg>}</div>',
+		'<div align=center>{<div align=left>foo</div><!-- bar -->}</div>',
+		'<div align=left>{<div align=center>foo</div><!-- bar -->}</div>',
 	],
 	//@}
 	justifyfull: [