Define indeterminate for insert*List
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Wed, 29 Jun 2011 09:08:33 -0600
changeset 342 c056a604c764
parent 341 b83b7d617b9b
child 343 919033b4af87
Define indeterminate for insert*List

Now everything that has either a state or value defined also has
indeterminate defined, except styleWithCss (where it doesn't make
sense). The meaning as a rule is that it's indeterminate if either the
state is true for part of the selection but not the whole selection, or
there are multiple values in the selection, as the case may be.
editcommands.html
implementation.js
source.html
--- a/editcommands.html	Tue Jun 28 15:39:29 2011 -0600
+++ b/editcommands.html	Wed Jun 29 09:08:33 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-28-june-2011>Work in Progress &mdash; Last Update 28 June 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-29-june-2011>Work in Progress &mdash; Last Update 29 June 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;ayg+spec@aryeh.name&gt;
@@ -339,7 +339,10 @@
 scripts to determine whether the selection is in a mixed or indeterminate
 state, like (for <a href=#the-bold-command>the <code title="">bold</code> command</a>) partly bold
 and partly not.  An editing toolbar with a "bold" button might show it
-differently if the selection is indeterminate.
+differently if the selection is indeterminate.  As a rule, the current
+selection is indeterminate for a command if either the command has a state
+which is true for part of the selection but not the whole selection, or it has
+a value which is different for different parts of the selection.
 
 <p>The <dfn id=querycommandstate() title=queryCommandState()><code>queryCommandState(<var title="">command</var>)</code></dfn>
 method on the <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> interface allows
@@ -3197,6 +3200,52 @@
   </ol>
 </ol>
 
+<p>The <dfn id="selection's-list-state">selection's list state</dfn> is returned by the following
+algorithm:
+
+<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 <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 <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>; <var title="">node</var> is not a
+  <a href=#potential-indentation-element>potential indentation element</a>; and <var title="">node</var> is either an
+  <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, or the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, or an
+  <a href=#allowed-child>allowed child</a> of "li".
+
+  <li>If <var title="">node list</var> is empty, return "none".
+
+  <li>If every member of <var title="">node list</var> is either an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or the
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code>, and none is a <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code> or 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 a <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>,
+  return "ol".
+
+  <li>If every member of <var title="">node list</var> is either a <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code> or the
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of a <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, and none is an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or 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 an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code>,
+  return "ul".
+  <!-- The previous two conditions are mutually exclusive, so the order is
+  actually irrelevant.  Clearly they could only both hold if no member of node
+  list is an ol or ul, so if they both held, every member would have to be both
+  the child of an ol and the child of a ul.  This is impossible unless the list
+  is empty, in which case we already aborted. -->
+
+  <li>If some member of <var title="">node list</var> is either an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or the
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> or <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 an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code>, and some member of <var title="">node
+  list</var> is either a <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code> or the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> or <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 a <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>,
+  return "mixed".
+
+  <li>If some member of <var title="">node list</var> is either an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or the
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> or <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 an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code>, return "mixed ol".
+
+  <li>If some member of <var title="">node list</var> is either a <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code> or the
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> or <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 a <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, return "mixed ul".
+
+  <li>Return "none".
+</ol>
+
 <p>The <dfn id=canonical-space-sequence>canonical space sequence</dfn> of length <var title="">n</var>, with boolean
 flags <var title="">non-breaking start</var> and <var title="">non-breaking end</var>, is
 returned by the following algorithm:
@@ -3546,7 +3595,7 @@
   and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offsets</a> of <var title="">range</var>.
 
   <li>If some <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container title="ancestor container">ancestor container</a> of <var title="">start node</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>, set
-  <var title="">start offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of the last such <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> in
+  <var title="">start offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of the first such <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> in
   <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>, and set <var title="">start node</var> to that <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>'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>Repeat the following steps:
@@ -3579,7 +3628,7 @@
   </ol>
 
   <li>If some <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container title="ancestor container">ancestor container</a> of <var title="">end node</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>, set
-  <var title="">end offset</var> to one plus the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of the last such <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> in
+  <var title="">end offset</var> to one plus the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of the first such <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> in
   <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>, and set <var title="">end node</var> to that <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>'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>Repeat the following steps:
@@ -4517,6 +4566,9 @@
 or "ul"):
 
 <ol>
+  <li>Let <var title="">mode</var> be "disable" if the <a href="#selection's-list-state">selection's list
+  state</a> is <var title="">tag name</var>, and "enable" otherwise.
+
   <li>Let <var title="">other tag name</var> be "ol" if <var title="">tag name</var> is "ul", and
   "ul" if <var title="">tag name</var> is "ol".
 
@@ -4557,11 +4609,8 @@
   case.
   -->
 
-  <li>If every member of <var title="">node list</var> is equal to or the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of
-  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> <var title="">tag name</var>, and no
-  member of <var title="">node list</var> is equal to or the <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 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> <var title="">other tag name</var>, then
-  while <var title="">node list</var> is not empty:
+  <li>If <var title="">mode</var> is "disable", then while <var title="">node list</var> is not
+  empty:
 
   <ol>
     <li>Let <var title="">sublist</var> be an empty 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>.
@@ -5806,46 +5855,24 @@
 <p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
 "ol".
 
-<p class=XXX>Consider defining indeterminate flag.
-<!-- Gecko sort of supports it, but unreliably, and no one else does.  Ditto
-insertUnorderedList, obviously. -->
-
-<p><a href=#state>State</a>:
+<p><a href=#indeterminate-flag>Indeterminate flag</a>: True if the <a href="#selection's-list-state">selection's list
+state</a> is "mixed" or "mixed ol", false otherwise.
 <!--
-Logically, this should return true if running the command would remove ordered
-lists, false if it would add them, just like for bold etc.  So I copy-paste the
-logic from there.  Note that when we actually run the command, we'll normalize
-sublists, but not just when we query.  This probably doesn't make a difference,
-but I'm not totally sure.
-
+Firefox 6.0a2 sort of supports this, but it throws exceptions most of the
+time.  It has the quirk that even if there are no ol's around, it will say it's
+indeterminate if there are some things that are ul's and some that are not
+lists at all, but this doesn't make sense and I don't duplicate it.  No one
+else supports indeterminate for insert*List, but it makes sense if you support
+state.
+-->
+
+<p><a href=#state>State</a>: True if the <a href="#selection's-list-state">selection's list state</a> is "ol",
+false otherwise.
+<!--
 IE9 throws exceptions in most cases, Firefox 6.0a2 in some cases as well, for
 no apparent reason.  Ignoring those, the spec basically matches all browsers,
 except with a few weird random mismatches that looked like browser bugs to me.
 -->
-<p class=XXX>Does the fact that we don't normalize sublists before running this
-mean that it might produce results that don't match the way the action will
-behave?
-
-<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 <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>, initially empty.
-
-  <li>For each <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> <var title="">node</var> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new range</var>,
-  if <var title="">node</var> is <a href=#editable>editable</a>; 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 not a <a href=#potential-indentation-element>potential indentation element</a>; and
-  either <var title="">node</var> is an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, or 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=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code>
-  or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, or it is an <a href=#allowed-child>allowed child</a> of "li"; then append
-  <var title="">node</var> to <var title="">node list</var>.
-
-  <li>If every member of <var title="">node list</var> is equal to or the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of
-  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> "ol", and no member of
-  <var title="">node list</var> is equal to or the <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 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> "ul", then return true.  Otherwise, return
-  false.
-</ol>
 
 
 <h3 id=the-insertparagraph-command><span class=secno>7.18 </span><dfn>The <code title="">insertParagraph</code> command</dfn></h3>
@@ -6292,32 +6319,11 @@
 <p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
 "ul".
 
-<p><a href=#state>State</a>:
-<!-- See insertOrderedList for comments. -->
-<p class=XXX>Does the fact that we don't normalize sublists before running this
-mean that it might produce results that don't match the way the action will
-behave?
-
-<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 <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>, initially empty.
-
-  <li>For each <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> <var title="">node</var> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new range</var>,
-  if <var title="">node</var> is <a href=#editable>editable</a>; 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 not a <a href=#potential-indentation-element>potential indentation element</a>; and
-  either <var title="">node</var> is an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, or 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=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code>
-  or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, or it is an <a href=#allowed-child>allowed child</a> of "li"; then append
-  <var title="">node</var> to <var title="">node list</var>.
-
-  <li>If every member of <var title="">node list</var> is equal to or the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of
-  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> "ul", and no member of
-  <var title="">node list</var> is equal to or the <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 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> "ol", then return true.  Otherwise, return
-  false.
-</ol>
+<p><a href=#indeterminate-flag>Indeterminate flag</a>: True if the <a href="#selection's-list-state">selection's list
+state</a> is "mixed" or "mixed ul", false otherwise.
+
+<p><a href=#state>State</a>: True if the <a href="#selection's-list-state">selection's list state</a> is "ul",
+false otherwise.
 
 
 <h3 id=the-justifycenter-command><span class=secno>7.21 </span><dfn>The <code title="">justifyCenter</code> command</dfn></h3>
--- a/implementation.js	Tue Jun 28 15:39:29 2011 -0600
+++ b/implementation.js	Wed Jun 29 09:08:33 2011 -0600
@@ -3601,6 +3601,77 @@
 	}
 }
 
+function getSelectionListState() {
+	// "Block-extend the active range, and let new range be the result."
+	var newRange = blockExtendRange(getActiveRange());
+
+	// "Let node list be a list of nodes, initially empty."
+	//
+	// "For each 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; node is not a potential indentation element; and node is
+	// either an ol or ul, or the child of an ol or ul, or an allowed child of
+	// "li"."
+	var nodeList = collectContainedNodes(newRange, function(node) {
+		return isEditable(node)
+			&& !isPotentialIndentationElement(node)
+			&& (isHtmlElement(node, ["ol", "ul"])
+			|| isHtmlElement(node.parentNode, ["ol", "ul"])
+			|| isAllowedChild(node, "li"));
+	});
+
+	// "If node list is empty, return "none"."
+	if (!nodeList.length) {
+		return "none";
+	}
+
+	// "If every member of node list is either an ol or the child of an ol, and
+	// none is a ul or an ancestor of a ul, return "ol"."
+	if (nodeList.every(function(node) { return isHtmlElement(node, "ol") || isHtmlElement(node.parentNode, "ol") })
+	&& !nodeList.some(function(node) { return isHtmlElement(node, "ul") || ("querySelector" in node && node.querySelector("ul")) })) {
+		return "ol";
+	}
+
+	// "If every member of node list is either a ul or the child of a ul, and
+	// none is an ol or an ancestor of an ol, return "ul"."
+	if (nodeList.every(function(node) { return isHtmlElement(node, "ul") || isHtmlElement(node.parentNode, "ul") })
+	&& !nodeList.some(function(node) { return isHtmlElement(node, "ol") || ("querySelector" in node && node.querySelector("ol")) })) {
+		return "ul";
+	}
+
+	var hasOl = nodeList.some(function(node) {
+		return isHtmlElement(node, "ol")
+			|| isHtmlElement(node.parentNode, "ol")
+			|| ("querySelector" in node && node.querySelector("ol"));
+	});
+	var hasUl = nodeList.some(function(node) {
+		return isHtmlElement(node, "ul")
+			|| isHtmlElement(node.parentNode, "ul")
+			|| ("querySelector" in node && node.querySelector("ul"));
+	});
+	// "If some member of node list is either an ol or the child or ancestor of
+	// an ol, and some member of node list is either a ul or the child or
+	// ancestor of a ul, return "mixed"."
+	if (hasOl && hasUl) {
+		return "mixed";
+	}
+
+	// "If some member of node list is either an ol or the child or ancestor of
+	// an ol, return "mixed ol"."
+	if (hasOl) {
+		return "mixed ol";
+	}
+
+	// "If some member of node list is either a ul or the child or ancestor of
+	// a ul, return "mixed ul"."
+	if (hasUl) {
+		return "mixed ul";
+	}
+
+	// "Return "none"."
+	return "none";
+}
+
 function canonicalSpaceSequence(n, nonBreakingStart, nonBreakingEnd) {
 	// "If n is zero, return the empty string."
 	if (n == 0) {
@@ -3967,7 +4038,7 @@
 	var endOffset = range.endOffset;
 
 	// "If some ancestor container of start node is an li, set start offset to
-	// the index of the last such li in tree order, and set start node to that
+	// the index of the first such li in tree order, and set start node to that
 	// li's parent."
 	for (
 		var ancestorContainer = startNode;
@@ -3977,7 +4048,6 @@
 		if (isHtmlElement(ancestorContainer, "LI")) {
 			startOffset = getNodeIndex(ancestorContainer);
 			startNode = ancestorContainer.parentNode;
-			break;
 		}
 	}
 
@@ -4016,7 +4086,7 @@
 	}
 
 	// "If some ancestor container of end node is an li, set end offset to one
-	// plus the index of the last such li in tree order, and set end node to
+	// plus the index of the first such li in tree order, and set end node to
 	// that li's parent."
 	for (
 		var ancestorContainer = endNode;
@@ -4026,7 +4096,6 @@
 		if (isHtmlElement(ancestorContainer, "LI")) {
 			endOffset = 1 + getNodeIndex(ancestorContainer);
 			endNode = ancestorContainer.parentNode;
-			break;
 		}
 	}
 
@@ -4689,6 +4758,10 @@
 //@{
 
 function toggleLists(tagName) {
+	// "Let mode be "disable" if the selection's list state is tag name, and
+	// "enable" otherwise."
+	var mode = getSelectionListState() == tagName ? "disable" : "enable";
+
 	var range = getActiveRange();
 	tagName = tagName.toUpperCase();
 
@@ -4749,12 +4822,8 @@
 		}
 	}
 
-	// "If every member of node list is equal to or the child of an HTML
-	// element with local name tag name, and no member of node list is equal to
-	// or the ancestor of an HTML element with local name other tag name, then
-	// while node list is not empty:"
-	if (nodeList.every(function(node) { return isHtmlElement(node, tagName) || isHtmlElement(node.parentNode, tagName) })
-	&& !nodeList.some(function(node) { return isHtmlElement(node, otherTagName) || node.querySelector(otherTagName) })) {
+	// "If mode is "disable", then while node list is not empty:"
+	if (mode == "disable") {
 		while (nodeList.length) {
 			// "Let sublist be an empty list of nodes."
 			var sublist = [];
@@ -5983,35 +6052,11 @@
 commands.insertorderedlist = {
 	// "Toggle lists with tag name "ol"."
 	action: function() { toggleLists("ol") },
-	state: function() {
-		// "Block-extend the active range, and let new range be the result."
-		var newRange = blockExtendRange(getActiveRange());
-
-		// "Let node list be a list of nodes, initially empty."
-		//
-		// "For each node node contained in new range, if node is editable; the
-		// last member of node list (if any) is not an ancestor of node; node
-		// is not a potential indentation element; and either node is an ol or
-		// ul, or its parent is an ol or ul, or it is an allowed child of "li";
-		// then append node to node list."
-		var nodeList = collectContainedNodes(newRange, function(node) {
-			return isEditable(node)
-				&& !isPotentialIndentationElement(node)
-				&& (isHtmlElement(node, ["ol", "ul"])
-				|| isHtmlElement(node.parentNode, ["ol", "ul"])
-				|| isAllowedChild(node, "li"));
-		});
-
-		// "If every member of node list is equal to or the child of an HTML
-		// element with local name "ol", and no member of node list is equal to
-		// or the ancestor of an HTML element with local name "ul", then return
-		// true. Otherwise, return false."
-		return nodeList.every(function(node) {
-			return (isHtmlElement(node, "ol") || isHtmlElement(node.parentNode, "ol"))
-				&& !isHtmlElement(node, "ul")
-				&& !node.querySelector("ul");
-		});
-	},
+	// "True if the selection's list state is "mixed" or "mixed ol", false
+	// otherwise."
+	indeterm: function() { return /^mixed( ol)?$/.test(getSelectionListState()) },
+	// "True if the selection's list state is "ol", false otherwise."
+	state: function() { return getSelectionListState() == "ol" },
 };
 //@}
 
@@ -6393,35 +6438,11 @@
 commands.insertunorderedlist = {
 	// "Toggle lists with tag name "ul"."
 	action: function() { toggleLists("ul") },
-	state: function() {
-		// "Block-extend the active range, and let new range be the result."
-		var newRange = blockExtendRange(getActiveRange());
-
-		// "Let node list be a list of nodes, initially empty."
-		//
-		// "For each node node contained in new range, if node is editable; the
-		// last member of node list (if any) is not an ancestor of node; node
-		// is not a potential indentation element; and either node is an ol or
-		// ul, or its parent is an ol or ul, or it is an allowed child of "li";
-		// then append node to node list."
-		var nodeList = collectContainedNodes(newRange, function(node) {
-			return isEditable(node)
-				&& !isPotentialIndentationElement(node)
-				&& (isHtmlElement(node, ["ol", "ul"])
-				|| isHtmlElement(node.parentNode, ["ol", "ul"])
-				|| isAllowedChild(node, "li"));
-		});
-
-		// "If every member of node list is equal to or the child of an HTML
-		// element with local name "ul", and no member of node list is equal to
-		// or the ancestor of an HTML element with local name "ol", then return
-		// true. Otherwise, return false."
-		return nodeList.every(function(node) {
-			return (isHtmlElement(node, "ul") || isHtmlElement(node.parentNode, "ul"))
-				&& !isHtmlElement(node, "ol")
-				&& !node.querySelector("ol");
-		});
-	},
+	// "True if the selection's list state is "mixed" or "mixed ul", false
+	// otherwise."
+	indeterm: function() { return /^mixed( ul)?$/.test(getSelectionListState()) },
+	// "True if the selection's list state is "ul", false otherwise."
+	state: function() { return getSelectionListState() == "ul" },
 };
 //@}
 
--- a/source.html	Tue Jun 28 15:39:29 2011 -0600
+++ b/source.html	Wed Jun 29 09:08:33 2011 -0600
@@ -275,7 +275,10 @@
 scripts to determine whether the selection is in a mixed or indeterminate
 state, like (for <span>the <code title>bold</code> command</span>) partly bold
 and partly not.  An editing toolbar with a "bold" button might show it
-differently if the selection is indeterminate.
+differently if the selection is indeterminate.  As a rule, the current
+selection is indeterminate for a command if either the command has a state
+which is true for part of the selection but not the whole selection, or it has
+a value which is different for different parts of the selection.
 
 <p>The <dfn
 title=queryCommandState()><code>queryCommandState(<var>command</var>)</code></dfn>
@@ -3182,6 +3185,52 @@
   </ol>
 </ol>
 
+<p>The <dfn>selection's list state</dfn> is returned by the following
+algorithm:
+
+<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 [[nodes]], initially empty.
+
+  <li>For each <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>; <var>node</var> is not a
+  <span>potential indentation element</span>; and <var>node</var> is either an
+  [[ol]] or [[ul]], or the [[child]] of an [[ol]] or [[ul]], or an
+  <span>allowed child</span> of "li".
+
+  <li>If <var>node list</var> is empty, return "none".
+
+  <li>If every member of <var>node list</var> is either an [[ol]] or the
+  [[child]] of an [[ol]], and none is a [[ul]] or an [[ancestor]] of a [[ul]],
+  return "ol".
+
+  <li>If every member of <var>node list</var> is either a [[ul]] or the
+  [[child]] of a [[ul]], and none is an [[ol]] or an [[ancestor]] of an [[ol]],
+  return "ul".
+  <!-- The previous two conditions are mutually exclusive, so the order is
+  actually irrelevant.  Clearly they could only both hold if no member of node
+  list is an ol or ul, so if they both held, every member would have to be both
+  the child of an ol and the child of a ul.  This is impossible unless the list
+  is empty, in which case we already aborted. -->
+
+  <li>If some member of <var>node list</var> is either an [[ol]] or the
+  [[child]] or [[ancestor]] of an [[ol]], and some member of <var>node
+  list</var> is either a [[ul]] or the [[child]] or [[ancestor]] of a [[ul]],
+  return "mixed".
+
+  <li>If some member of <var>node list</var> is either an [[ol]] or the
+  [[child]] or [[ancestor]] of an [[ol]], return "mixed ol".
+
+  <li>If some member of <var>node list</var> is either a [[ul]] or the
+  [[child]] or [[ancestor]] of a [[ul]], return "mixed ul".
+
+  <li>Return "none".
+</ol>
+
 <p>The <dfn>canonical space sequence</dfn> of length <var>n</var>, with boolean
 flags <var>non-breaking start</var> and <var>non-breaking end</var>, is
 returned by the following algorithm:
@@ -3531,7 +3580,7 @@
   and [[bpoffsets]] of <var>range</var>.
 
   <li>If some [[ancestorcontainer]] of <var>start node</var> is an [[li]], set
-  <var>start offset</var> to the [[index]] of the last such [[li]] in
+  <var>start offset</var> to the [[index]] of the first such [[li]] in
   [[treeorder]], and set <var>start node</var> to that [[li]]'s [[parent]].
 
   <li>Repeat the following steps:
@@ -3564,7 +3613,7 @@
   </ol>
 
   <li>If some [[ancestorcontainer]] of <var>end node</var> is an [[li]], set
-  <var>end offset</var> to one plus the [[index]] of the last such [[li]] in
+  <var>end offset</var> to one plus the [[index]] of the first such [[li]] in
   [[treeorder]], and set <var>end node</var> to that [[li]]'s [[parent]].
 
   <li>Repeat the following steps:
@@ -4506,6 +4555,9 @@
 or "ul"):
 
 <ol>
+  <li>Let <var>mode</var> be "disable" if the <span>selection's list
+  state</span> is <var>tag name</var>, and "enable" otherwise.
+
   <li>Let <var>other tag name</var> be "ol" if <var>tag name</var> is "ul", and
   "ul" if <var>tag name</var> is "ol".
 
@@ -4546,11 +4598,8 @@
   case.
   -->
 
-  <li>If every member of <var>node list</var> is equal to or the [[child]] of
-  an <span>HTML element</span> with [[localname]] <var>tag name</var>, and no
-  member of <var>node list</var> is equal to or the [[ancestor]] of an
-  <span>HTML element</span> with [[localname]] <var>other tag name</var>, then
-  while <var>node list</var> is not empty:
+  <li>If <var>mode</var> is "disable", then while <var>node list</var> is not
+  empty:
 
   <ol>
     <li>Let <var>sublist</var> be an empty list of [[nodes]].
@@ -5818,46 +5867,24 @@
 <p><span>Action</span>: <span>Toggle lists</span> with <var>tag name</var>
 "ol".
 
-<p class=XXX>Consider defining indeterminate flag.
-<!-- Gecko sort of supports it, but unreliably, and no one else does.  Ditto
-insertUnorderedList, obviously. -->
-
-<p><span>State</span>:
+<p><span>Indeterminate flag</span>: True if the <span>selection's list
+state</span> is "mixed" or "mixed ol", false otherwise.
 <!--
-Logically, this should return true if running the command would remove ordered
-lists, false if it would add them, just like for bold etc.  So I copy-paste the
-logic from there.  Note that when we actually run the command, we'll normalize
-sublists, but not just when we query.  This probably doesn't make a difference,
-but I'm not totally sure.
-
+Firefox 6.0a2 sort of supports this, but it throws exceptions most of the
+time.  It has the quirk that even if there are no ol's around, it will say it's
+indeterminate if there are some things that are ul's and some that are not
+lists at all, but this doesn't make sense and I don't duplicate it.  No one
+else supports indeterminate for insert*List, but it makes sense if you support
+state.
+-->
+
+<p><span>State</span>: True if the <span>selection's list state</span> is "ol",
+false otherwise.
+<!--
 IE9 throws exceptions in most cases, Firefox 6.0a2 in some cases as well, for
 no apparent reason.  Ignoring those, the spec basically matches all browsers,
 except with a few weird random mismatches that looked like browser bugs to me.
 -->
-<p class=XXX>Does the fact that we don't normalize sublists before running this
-mean that it might produce results that don't match the way the action will
-behave?
-
-<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 [[nodes]], initially empty.
-
-  <li>For each [[node]] <var>node</var> [[contained]] in <var>new range</var>,
-  if <var>node</var> is <span>editable</span>; the last member of <var>node
-  list</var> (if any) is not an [[ancestor]] of <var>node</var>;
-  <var>node</var> is not a <span>potential indentation element</span>; and
-  either <var>node</var> is an [[ol]] or [[ul]], or its [[parent]] is an [[ol]]
-  or [[ul]], or it is an <span>allowed child</span> of "li"; then append
-  <var>node</var> to <var>node list</var>.
-
-  <li>If every member of <var>node list</var> is equal to or the [[child]] of
-  an <span>HTML element</span> with [[localname]] "ol", and no member of
-  <var>node list</var> is equal to or the [[ancestor]] of an <span>HTML
-  element</span> with [[localname]] "ul", then return true.  Otherwise, return
-  false.
-</ol>
 <!-- @} -->
 
 <h3><dfn>The <code title>insertParagraph</code> command</dfn></h3>
@@ -6312,32 +6339,11 @@
 <p><span>Action</span>: <span>Toggle lists</span> with <var>tag name</var>
 "ul".
 
-<p><span>State</span>:
-<!-- See insertOrderedList for comments. -->
-<p class=XXX>Does the fact that we don't normalize sublists before running this
-mean that it might produce results that don't match the way the action will
-behave?
-
-<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 [[nodes]], initially empty.
-
-  <li>For each [[node]] <var>node</var> [[contained]] in <var>new range</var>,
-  if <var>node</var> is <span>editable</span>; the last member of <var>node
-  list</var> (if any) is not an [[ancestor]] of <var>node</var>;
-  <var>node</var> is not a <span>potential indentation element</span>; and
-  either <var>node</var> is an [[ol]] or [[ul]], or its [[parent]] is an [[ol]]
-  or [[ul]], or it is an <span>allowed child</span> of "li"; then append
-  <var>node</var> to <var>node list</var>.
-
-  <li>If every member of <var>node list</var> is equal to or the [[child]] of
-  an <span>HTML element</span> with [[localname]] "ul", and no member of
-  <var>node list</var> is equal to or the [[ancestor]] of an <span>HTML
-  element</span> with [[localname]] "ol", then return true.  Otherwise, return
-  false.
-</ol>
+<p><span>Indeterminate flag</span>: True if the <span>selection's list
+state</span> is "mixed" or "mixed ul", false otherwise.
+
+<p><span>State</span>: True if the <span>selection's list state</span> is "ul",
+false otherwise.
 <!-- @} -->
 
 <h3><dfn>The <code title>justifyCenter</code> command</dfn></h3>