Respect editability when making changes
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Wed, 13 Apr 2011 12:30:04 -0600
changeset 66 cd94129545fa
parent 65 bc18f082f7f4
child 67 3334e09907f1
Respect editability when making changes
autoimplementation.html
editcommands.html
implementation.js
source.html
--- a/autoimplementation.html	Wed Apr 13 10:53:15 2011 -0600
+++ b/autoimplementation.html	Wed Apr 13 12:30:04 2011 -0600
@@ -80,6 +80,13 @@
 		'{<p><p> <p>foo</p>}',
 		'foo[bar<i>baz]qoz</i>quz',
 
+		'foo<span contenteditable=false>[bar]</span>baz',
+		'fo[o<span contenteditable=false>bar</span>b]az',
+		'fo[<b>o</b><span contenteditable=false>bar</span><b>b</b>]az',
+		'<span contenteditable=false>foo<span contenteditable=true>[bar]</span>baz</span>',
+		'<span contenteditable=false>fo[o<span contenteditable=true>bar</span>b]az</span>',
+		'<span contenteditable=false>fo[<b>o<span contenteditable=true>bar</span>b</b>]az</span>',
+
 		'<table><tbody><tr><td>foo<td>b[a]r<td>baz</table>',
 		'<table><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table>',
 		'<table><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table>',
--- a/editcommands.html	Wed Apr 13 10:53:15 2011 -0600
+++ b/editcommands.html	Wed Apr 13 12:30:04 2011 -0600
@@ -165,10 +165,6 @@
   Document, comments that are children of a Document, that sort of thing.  Not
   essential for prototyping, but needs to be cleaned up eventually.
 
-  <li>I don't pay attention to whether designMode/contenteditable is actually
-  set.  I should be doing things like not doing anything if the selection isn't
-  editable, making sure not to break out of contenteditable regions, etc.
-
   <li>I haven't yet paid any attention to value parsing in most cases.  This is
   going to be important to specify exactly in some cases, like with colors.
 
@@ -185,10 +181,16 @@
 
 <h2 id=definitions><span class=secno>3 </span>Definitions</h2>
 
-<p>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> is an <dfn id=html-element>HTML element</dfn> if it is an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> whose
-<a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-namespace title=concept-element-namespace>namespace</a> is 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>.  An <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#attr>Attr</a></code> is an <dfn id=html-attribute>HTML attribute</dfn> if its
-<a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-attr-namespace title=concept-attr-namespace>namespace</a> is
-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>.
+<p>An <dfn id=html-element>HTML element</dfn> 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> whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-namespace title=concept-element-namespace>namespace</a> is 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>.
+
+<p>Something is <dfn id=editable>editable</dfn> if either it is an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> with a <code class=external data-anolis-spec=html title=attr-contenteditable><a href=http://www.whatwg.org/html/#attr-contenteditable>contenteditable</a></code>
+attribute set to the true state; or it is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#document>Document</a></code> whose <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#designmode>designMode</a></code> is enabled; or it is a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> whose
+<a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is <a href=#editable>editable</a>, but which does not have a <code class=external data-anolis-spec=html title=attr-contenteditable><a href=http://www.whatwg.org/html/#attr-contenteditable>contenteditable</a></code>
+attribute set to the false state.
+
+<p>An <dfn id=editing-host>editing host</dfn> is a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> that is <a href=#editable>editable</a>, and
+whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is not <a href=#editable>editable</a>.
 
 <p>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> is <dfn id=effectively-contained>effectively contained</dfn> in a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> if either it
 is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>; or it is the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>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>
@@ -204,11 +206,11 @@
 if they're effectively contained: it might be that the new node created by
 splitText() is returned. -->
 
-<p>An <dfn id=unwrappable-element>unwrappable element</dfn> is an <a href=#html-element>HTML element</a> which may
-not be used where only <a class=external data-anolis-spec=html href=http://www.whatwg.org/html/#phrasing-content>phrasing content</a> is expected (not counting unknown or
+<p>An <dfn id=unwrappable-node>unwrappable node</dfn> is an <a href=#html-element>HTML element</a> which may not
+be used where only <a class=external data-anolis-spec=html href=http://www.whatwg.org/html/#phrasing-content>phrasing content</a> is expected (not counting unknown or
 obsolete elements, which cannot be used at all); or any <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> whose
 display property computes to something other than "inline", "inline-block", or
-"inline-table".
+"inline-table"; or any <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is not <a href=#editable>editable</a>.
 
 <p class=XXX>Currently when we hit an unwrappable element, we ignore it and
 alter its children.  Alternatively, if we would otherwise create a span with a
@@ -536,8 +538,8 @@
 
 
 <h2 id="clearing-an-element's-value"><span class=secno>5 </span>Clearing an element's value</h2>
-<p>When a user agent is to <dfn id=clear-the-value>clear the value</dfn> of an element, it must run
-the following steps:
+<p>When a user agent is to <dfn id=clear-the-value>clear the value</dfn> of an element
+<var title="">element</var>, it must run the following steps:
 
 <p class=note>Clearing the value of an element can remove it from its parent
 and put other nodes in its place.  When implementations do something like clear
@@ -550,10 +552,6 @@
 -->
 
 <ol>
-  <li>Let <var title="">element</var> be the <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> to be cleared.
-
-  <li>Let <var title="">command</var> be as in the invoking algorithm.
-
   <li>If <var title="">element</var>'s <a href=#specified-value>specified value</a> for
   <var title="">command</var> is null, return the empty list.  <!-- We want to abort
   early so that we don't try unsetting background-color on a non-inline
@@ -677,8 +675,8 @@
 
   <li>Let <var title="">ancestor list</var> be a list of <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>, initially empty.
 
-  <li>While <var title="">current ancestor</var> is an <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
-  <a href=#effective-value>effective value</a> of <var title="">property</var> is not <var title="">new
+  <li>While <var title="">current ancestor</var> is an <a href=#editable>editable</a> <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>
+  and the <a href=#effective-value>effective value</a> of <var title="">property</var> is not <var title="">new
   value</var> on it, append <var title="">current ancestor</var> to <var title="">ancestor
   list</var>, then set <var title="">current ancestor</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
 
@@ -764,8 +762,8 @@
   <li>If <var title="">new value</var> is null, abort this algorithm.
 
   <li>If <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>, <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code>, <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code>, or
-  <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction>ProcessingInstruction</a></code> node, and is not an <a href=#unwrappable-element>unwrappable
-  element</a>:
+  <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction>ProcessingInstruction</a></code> node, and is not an <a href=#unwrappable-node>unwrappable
+  node</a>:
 
   <ol>
     <!-- Even if the value matches, we stick it in a preceding sibling if
@@ -868,7 +866,7 @@
   <li>If the <a href=#effective-value>effective value</a> of <var title="">command</var> is <var title="">new
   value</var> on <var title="">node</var>, abort this algorithm.
 
-  <li>If <var title="">node</var> is an <a href=#unwrappable-element>unwrappable element</a>:
+  <li>If <var title="">node</var> is an <a href=#unwrappable-node>unwrappable node</a>:
 
   <ol>
     <li>Let <var title="">children</var> be all <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a> of <var title="">node</var>,
@@ -1102,8 +1100,8 @@
 often we'll style several consecutive siblings in succession.  In that case,
 the element created for the first can be reused for the later ones.
 
-<p>This last step works a bit differently if the node is an <a href=#unwrappable-element>unwrappable
-element</a>.  In that case, wrapping it in a simple styling element would
+<p>This last step works a bit differently if the node is an <a href=#unwrappable-node>unwrappable
+node</a>.  In that case, wrapping it in a simple styling element would
 make the document less conforming than it already was.  Instead, we recursively
 force style on its children.  The recursion will terminate when we hit a node
 that's wrappable, or when there are no further descendants.
@@ -1133,6 +1131,49 @@
   effort.  Is execCommand() even supposed to work on things that don't descend
   from a document?  Needs investigation.
 
+  <li>If <var title="">node</var> is not <a href=#editable>editable</a>:
+  <!--
+  IE9: Allows everything to be modified by execCommand(), regardless of whether
+    it's editable.
+  Firefox 4.0: Ignores execCommand() if the start and end of the selection are
+    not both editable.  If the start and end are editable but something in the
+    middle is not, seems to relocate the non-editable part in the middle or
+    something like that.
+  Chrome 12 dev: Ignores execCommand() if the start and end of the selection
+    are not both editable.  If the start and end are editable but something in
+    the middle is not, applies the given command but skips the non-editable
+    parts.  But the state doesn't ignore the non-editable parts, so if you bold
+    such a selection you can't unbold it, for instance, since the middle part
+    will remain bold (so it will keep on trying to bold it instead of switching
+    to unbold).
+  Opera 11.00: Ignores execCommand() if the start and end of the selection are
+    not both editable.  If the start and end are editable but something in the
+    middle is not, applies the command to everything, even the non-editable
+    part.
+
+  I chose to go with the non-IE behavior, per this discussion:
+  http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2011-April/031147.html
+  Ignoring non-editable things is convenient for the common use-case of an
+  editor, where you don't want the user to bold random parts of the UI when
+  they hit the bold button.  For cases where it's not desired, you can always
+  turn designMode on briefly before using execCommand(), so the non-IE behavior
+  is a lot easier to work around than the IE behavior.
+
+  I don't see the value in ever just ignoring execCommand().  If the start and
+  end are not editable, I'm going to say you should still style any editable
+  nodes in between.  I'm also going to ignore non-editable nodes for the
+  purposes of determining state, so (for instance) if all the editable nodes
+  are bolded, it will unbold instead of bolding.
+  -->
+
+  <ol>
+    <li>Let <var title="">children</var> be the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a> of <var title="">node</var>.
+
+    <li><a href=#set-the-value>Set the value</a> of each member of <var title="">children</var>.
+
+    <li>Abort this algorithm.
+  </ol>
+
   <li>If <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>:
 
   <ol>
@@ -1140,8 +1181,7 @@
     nodes</var> be the result.
 
     <li>For each <var title="">new node</var> in <var title="">new nodes</var>,
-    <a href=#set-the-value>set the value</a> of <var title="">new node</var>, with the same inputs as
-    this invocation of the algorithm.
+    <a href=#set-the-value>set the value</a> of <var title="">new node</var>.
 
     <li>If <var title="">node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is null, abort this algorithm.
   </ol>
@@ -1274,8 +1314,8 @@
 Otherwise, <a href=#set-the-value title="set the value">set their value</a> with <var title="">new
 value</var> "bold".
 
-<dd><strong>State</strong>: True if every <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node that is
-<a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
+<dd><strong>State</strong>: True if every <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#text>Text</a></code> node
+that is <a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
 value</a> at least 700.  Otherwise false.
 <!-- For bold and similar commands, IE 9 RC seems to consider the state true or
 false depending on the first element.  All other browsers follow the same
@@ -1695,8 +1735,8 @@
 Otherwise, <a href=#set-the-value title="set the value">set their value</a> with <var title="">new
 value</var> "italic".
 
-<dd><strong>State</strong>: True if every <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node that is
-<a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
+<dd><strong>State</strong>: True if every <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#text>Text</a></code> node
+that is <a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
 value</a> either "italic" or "oblique".  Otherwise false.
 
 <dd><strong>Value</strong>: Always the empty string.
@@ -1838,8 +1878,8 @@
 
 <p class=XXX>Has all the same problems as underline.
 
-<dd><strong>State</strong>: True if every <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node that is
-<a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
+<dd><strong>State</strong>: True if every <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#text>Text</a></code> node
+that is <a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
 value</a> "line-through".  Otherwise false.
 
 <dd><strong>Value</strong>: Always the empty string.
@@ -1869,8 +1909,8 @@
 <a href=#set-the-value>set the value</a> of each returned <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> with <var title="">new value</var>
 "sub".
 
-<dd><strong>State</strong>: True if every <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node that is
-<a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
+<dd><strong>State</strong>: True if every <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#text>Text</a></code> node
+that is <a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
 value</a> "sub".  Otherwise false.
 
 <dd><strong>Value</strong>:
@@ -1888,8 +1928,8 @@
 <a href=#set-the-value>set the value</a> of each returned <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> with <var title="">new value</var>
 "super".
 
-<dd><strong>State</strong>: True if every <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node that is
-<a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
+<dd><strong>State</strong>: True if every <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#text>Text</a></code> node
+that is <a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
 value</a> "super".  Otherwise false.
 
 <dd><strong>Value</strong>:
@@ -1938,7 +1978,7 @@
   underline letter-by-letter, it probably will vary.  But sometimes when they
   underline a bunch of text at once it will also vary, if the algorithm decides
   to create multiple elements for whatever reason (like an intervening
-  unwrappable element).  This is unlikely to match user expectations.  There's
+  unwrappable node).  This is unlikely to match user expectations.  There's
   not much we can do about this without entirely revamping text-decoration, so
   we'll have to live with it.
 
@@ -1953,8 +1993,8 @@
 may be.
 </div>
 
-<dd><strong>State</strong>: True if every <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node that is
-<a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
+<dd><strong>State</strong>: True if every <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#text>Text</a></code> node
+that is <a href=#effectively-contained>effectively contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> has <a href=#effective-value>effective
 value</a> "underline".  Otherwise false.
 
 <dd><strong>Value</strong>: Always the empty string.
--- a/implementation.js	Wed Apr 13 10:53:15 2011 -0600
+++ b/implementation.js	Wed Apr 13 12:30:04 2011 -0600
@@ -286,14 +286,32 @@
 // Things defined in the edit command spec (i.e., the interesting stuff)
 
 
-// "A Node is an HTML element if it is an Element whose namespace is the HTML
-// namespace."
+// "An HTML element is an Element whose namespace is the HTML namespace."
 function isHtmlElement(node) {
 	return node
 		&& node.nodeType == Node.ELEMENT_NODE
 		&& isHtmlNamespace(node.namespaceURI);
 }
 
+// "Something is editable if either it is an Element with a contenteditable
+// attribute set to the true state; or it is a Document whose designMode is
+// enabled; or it is a node whose parent is editable, but which does not have a
+// contenteditable attribute set to the false state."
+function isEditable(node) {
+	// This is slightly a lie, because we're excluding non-HTML elements with
+	// contentEditable attributes.  Maybe we want to, though . . .
+	return (node instanceof Element && node.contentEditable == "true")
+		|| (node instanceof Document && node.designMode == "on")
+		|| (node instanceof Node && node.contentEditable !== "false" && isEditable(node.parentNode));
+}
+
+// "An editing host is a node that is editable, and whose parent is not
+// editable."
+function isEditingHost(node) {
+	return node instanceof Node
+		&& isEditable(node)
+		&& !isEditable(node.parentNode);
+}
 
 /**
  * "A Node is effectively contained in a Range if either it is contained in the
@@ -327,14 +345,23 @@
 	return false;
 }
 
-// "An unwrappable element is an HTML element which may not be used where only
+// "An unwrappable node is an HTML element which may not be used where only
 // phrasing content is expected (not counting unknown or obsolete elements,
 // which cannot be used at all); or any Element whose display property computes
-// to something other than "inline", "inline-block", or "inline-table"."
+// to something other than "inline", "inline-block", or "inline-table"; or any
+// node whose parent is not editable."
 //
 // I don't bother implementing this exactly, just well enough for testing.
-function isUnwrappableElement(node) {
-	if (!node || node.nodeType != Node.ELEMENT_NODE) {
+function isUnwrappableNode(node) {
+	if (!node) {
+		return false;
+	}
+
+	if (!isEditable(node.parentNode)) {
+		return true;
+	}
+
+	if (node.nodeType != Node.ELEMENT_NODE) {
 		return false;
 	}
 
@@ -1112,10 +1139,10 @@
 	// "Let ancestor list be a list of Nodes, initially empty."
 	var ancestorList = [];
 
-	// "While current ancestor is an Element and the effective value of
-	// command is not new value on it, append current ancestor to ancestor
+	// "While current ancestor is an editable Element and the effective value
+	// of command is not new value on it, append current ancestor to ancestor
 	// list, then set current ancestor to its parent."
-	while (currentAncestor
+	while (isEditable(currentAncestor)
 	&& currentAncestor.nodeType == Node.ELEMENT_NODE
 	&& !valuesEqual(command, getEffectiveValue(currentAncestor, command), newValue)) {
 		ancestorList.push(currentAncestor);
@@ -1209,12 +1236,12 @@
 	}
 
 	// "If node is an Element, Text, Comment, or ProcessingInstruction node,
-	// and is not an unwrappable element:"
+	// and is not an unwrappable node:"
 	if ((node.nodeType == Node.ELEMENT_NODE
 	|| node.nodeType == Node.TEXT_NODE
 	|| node.nodeType == Node.COMMENT_NODE
 	|| node.nodeType == Node.PROCESSING_INSTRUCTION_NODE)
-	&& !isUnwrappableElement(node)) {
+	&& !isUnwrappableNode(node)) {
 		// "Let candidate be node's previousSibling."
 		var candidate = node.previousSibling;
 
@@ -1331,8 +1358,8 @@
 		return;
 	}
 
-	// "If node is an unwrappable element:"
-	if (isUnwrappableElement(node)) {
+	// "If node is an unwrappable node:"
+	if (isUnwrappableNode(node)) {
 		// "Let children be all children of node, omitting any that are
 		// Elements whose specified value for command is neither null nor
 		// equal to new value."
@@ -1615,6 +1642,20 @@
 		return;
 	}
 
+	// "If node is not editable:"
+	if (!isEditable(node)) {
+		// "Let children be the children of node."
+		var children = Array.prototype.slice.call(node.childNodes);
+
+		// "Set the value of each member of children."
+		for (var i = 0; i < children.length; i++) {
+			setNodeValue(children[i], command, newValue);
+		}
+
+		// "Abort this algorithm."
+		return;
+	}
+
 	// "If node is an Element:"
 	if (node.nodeType == Node.ELEMENT_NODE) {
 		// "Clear the value of node, and let new nodes be the result."
@@ -2209,9 +2250,13 @@
 			continue;
 		}
 
+		if (!isEditable(node)) {
+			continue;
+		}
+
 		if (command == "bold") {
-			// "True if every Text node that is effectively contained in the
-			// range has effective value at least 700. Otherwise false."
+			// "True if every editable Text node that is effectively contained
+			// in the range has effective value at least 700. Otherwise false."
 			var fontWeight = getEffectiveValue(node, command);
 			if (fontWeight !== "bold"
 			&& fontWeight !== "700"
@@ -2220,8 +2265,8 @@
 				return false;
 			}
 		} else if (command == "italic") {
-			// "True if every Text node that is effectively contained in the
-			// range has effective value either "italic" or "oblique".
+			// "True if every editable Text node that is effectively contained
+			// in the range has effective value either "italic" or "oblique".
 			// Otherwise false."
 			var fontStyle = getEffectiveValue(node, command);
 			if (fontStyle !== "italic"
@@ -2229,29 +2274,30 @@
 				return false;
 			}
 		} else if (command == "strikethrough") {
-			// "True if every Text node that is effectively contained in the
-			// range has effective value "line-through". Otherwise false."
+			// "True if every editable Text node that is effectively contained
+			// in the range has effective value "line-through". Otherwise
+			// false."
 			var textDecoration = getEffectiveValue(node, command);
 			if (textDecoration !== "line-through") {
 				return false;
 			}
 		} else if (command == "underline") {
-			// "True if every Text node that is effectively contained in the
-			// range has effective value "underline". Otherwise false."
+			// "True if every editable Text node that is effectively contained
+			// in the range has effective value "underline". Otherwise false."
 			var textDecoration = getEffectiveValue(node, command);
 			if (textDecoration !== "underline") {
 				return false;
 			}
 		} else if (command == "subscript") {
-			// "True if every Text node that is effectively contained in the
-			// range has effective value "sub". Otherwise false."
+			// "True if every editable Text node that is effectively contained
+			// in the range has effective value "sub". Otherwise false."
 			var verticalAlign = getEffectiveValue(node, command);
 			if (verticalAlign !== "sub") {
 				return false;
 			}
 		} else if (command == "superscript") {
-			// "True if every Text node that is effectively contained in the
-			// range has effective value "super". Otherwise false."
+			// "True if every editable Text node that is effectively contained
+			// in the range has effective value "super". Otherwise false."
 			var verticalAlign = getEffectiveValue(node, command);
 			if (verticalAlign !== "super") {
 				return false;
--- a/source.html	Wed Apr 13 10:53:15 2011 -0600
+++ b/source.html	Wed Apr 13 12:30:04 2011 -0600
@@ -153,10 +153,6 @@
   Document, comments that are children of a Document, that sort of thing.  Not
   essential for prototyping, but needs to be cleaned up eventually.
 
-  <li>I don't pay attention to whether designMode/contenteditable is actually
-  set.  I should be doing things like not doing anything if the selection isn't
-  editable, making sure not to break out of contenteditable regions, etc.
-
   <li>I haven't yet paid any attention to value parsing in most cases.  This is
   going to be important to specify exactly in some cases, like with colors.
 
@@ -173,11 +169,19 @@
 
 <h2>Definitions</h2>
 
-<p>A [[node]] is an <dfn>HTML element</dfn> if it is an [[element]] whose
-[[namespace]] is the [[htmlnamespace]].  An <code
-data-anolis-spec=domcore>Attr</code> is an <dfn>HTML attribute</dfn> if its
-<span data-anolis-spec=domcore title=concept-attr-namespace>namespace</span> is
-the [[htmlnamespace]].
+<p>An <dfn>HTML element</dfn> is an [[element]] whose [[namespace]] is the
+[[htmlnamespace]].
+
+<p>Something is <dfn>editable</dfn> if either it is an [[element]] with a <code
+data-anolis-spec=html title=attr-contenteditable>contenteditable</code>
+attribute set to the true state; or it is a [[document]] whose <code
+data-anolis-spec=html>designMode</code> is enabled; or it is a [[node]] whose
+[[parent]] is <span>editable</span>, but which does not have a <code
+data-anolis-spec=html title=attr-contenteditable>contenteditable</code>
+attribute set to the false state.
+
+<p>An <dfn>editing host</dfn> is a [[node]] that is <span>editable</span>, and
+whose [[parent]] is not <span>editable</span>.
 
 <p>A [[node]] is <dfn>effectively contained</dfn> in a [[range]] if either it
 is [[contained]] in the [[range]]; or it is the [[range]]'s [[rangestart]]
@@ -193,11 +197,11 @@
 if they're effectively contained: it might be that the new node created by
 splitText() is returned. -->
 
-<p>An <dfn>unwrappable element</dfn> is an <span>HTML element</span> which may
-not be used where only [[phrasingcontent]] is expected (not counting unknown or
+<p>An <dfn>unwrappable node</dfn> is an <span>HTML element</span> which may not
+be used where only [[phrasingcontent]] is expected (not counting unknown or
 obsolete elements, which cannot be used at all); or any [[element]] whose
 display property computes to something other than "inline", "inline-block", or
-"inline-table".
+"inline-table"; or any [[node]] whose [[parent]] is not <span>editable</span>.
 
 <p class=XXX>Currently when we hit an unwrappable element, we ignore it and
 alter its children.  Alternatively, if we would otherwise create a span with a
@@ -529,8 +533,8 @@
 
 
 <h2>Clearing an element's value</h2>
-<p>When a user agent is to <dfn>clear the value</dfn> of an element, it must run
-the following steps:
+<p>When a user agent is to <dfn>clear the value</dfn> of an element
+<var>element</var>, it must run the following steps:
 
 <p class=note>Clearing the value of an element can remove it from its parent
 and put other nodes in its place.  When implementations do something like clear
@@ -543,10 +547,6 @@
 -->
 
 <ol>
-  <li>Let <var>element</var> be the [[element]] to be cleared.
-
-  <li>Let <var>command</var> be as in the invoking algorithm.
-
   <li>If <var>element</var>'s <span>specified value</span> for
   <var>command</var> is null, return the empty list.  <!-- We want to abort
   early so that we don't try unsetting background-color on a non-inline
@@ -670,8 +670,8 @@
 
   <li>Let <var>ancestor list</var> be a list of [[nodes]], initially empty.
 
-  <li>While <var>current ancestor</var> is an [[element]] and the
-  <span>effective value</span> of <var>property</var> is not <var>new
+  <li>While <var>current ancestor</var> is an <span>editable</span> [[element]]
+  and the <span>effective value</span> of <var>property</var> is not <var>new
   value</var> on it, append <var>current ancestor</var> to <var>ancestor
   list</var>, then set <var>current ancestor</var> to its [[parent]].
 
@@ -759,7 +759,7 @@
 
   <li>If <var>node</var> is an [[element]], [[text]], [[comment]], or
   [[processinginstruction]] node, and is not an <span>unwrappable
-  element</span>:
+  node</span>:
 
   <ol>
     <!-- Even if the value matches, we stick it in a preceding sibling if
@@ -862,7 +862,7 @@
   <li>If the <span>effective value</span> of <var>command</var> is <var>new
   value</var> on <var>node</var>, abort this algorithm.
 
-  <li>If <var>node</var> is an <span>unwrappable element</span>:
+  <li>If <var>node</var> is an <span>unwrappable node</span>:
 
   <ol>
     <li>Let <var>children</var> be all [[children]] of <var>node</var>,
@@ -1113,7 +1113,7 @@
 the element created for the first can be reused for the later ones.
 
 <p>This last step works a bit differently if the node is an <span>unwrappable
-element</span>.  In that case, wrapping it in a simple styling element would
+node</span>.  In that case, wrapping it in a simple styling element would
 make the document less conforming than it already was.  Instead, we recursively
 force style on its children.  The recursion will terminate when we hit a node
 that's wrappable, or when there are no further descendants.
@@ -1144,6 +1144,49 @@
   effort.  Is execCommand() even supposed to work on things that don't descend
   from a document?  Needs investigation.
 
+  <li>If <var>node</var> is not <span>editable</span>:
+  <!--
+  IE9: Allows everything to be modified by execCommand(), regardless of whether
+    it's editable.
+  Firefox 4.0: Ignores execCommand() if the start and end of the selection are
+    not both editable.  If the start and end are editable but something in the
+    middle is not, seems to relocate the non-editable part in the middle or
+    something like that.
+  Chrome 12 dev: Ignores execCommand() if the start and end of the selection
+    are not both editable.  If the start and end are editable but something in
+    the middle is not, applies the given command but skips the non-editable
+    parts.  But the state doesn't ignore the non-editable parts, so if you bold
+    such a selection you can't unbold it, for instance, since the middle part
+    will remain bold (so it will keep on trying to bold it instead of switching
+    to unbold).
+  Opera 11.00: Ignores execCommand() if the start and end of the selection are
+    not both editable.  If the start and end are editable but something in the
+    middle is not, applies the command to everything, even the non-editable
+    part.
+
+  I chose to go with the non-IE behavior, per this discussion:
+  http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2011-April/031147.html
+  Ignoring non-editable things is convenient for the common use-case of an
+  editor, where you don't want the user to bold random parts of the UI when
+  they hit the bold button.  For cases where it's not desired, you can always
+  turn designMode on briefly before using execCommand(), so the non-IE behavior
+  is a lot easier to work around than the IE behavior.
+
+  I don't see the value in ever just ignoring execCommand().  If the start and
+  end are not editable, I'm going to say you should still style any editable
+  nodes in between.  I'm also going to ignore non-editable nodes for the
+  purposes of determining state, so (for instance) if all the editable nodes
+  are bolded, it will unbold instead of bolding.
+  -->
+
+  <ol>
+    <li>Let <var>children</var> be the [[children]] of <var>node</var>.
+
+    <li><span>Set the value</span> of each member of <var>children</var>.
+
+    <li>Abort this algorithm.
+  </ol>
+
   <li>If <var>node</var> is an [[element]]:
 
   <ol>
@@ -1151,8 +1194,7 @@
     nodes</var> be the result.
 
     <li>For each <var>new node</var> in <var>new nodes</var>,
-    <span>set the value</span> of <var>new node</var>, with the same inputs as
-    this invocation of the algorithm.
+    <span>set the value</span> of <var>new node</var>.
 
     <li>If <var>node</var>'s [[parent]] is null, abort this algorithm.
   </ol>
@@ -1287,8 +1329,8 @@
 Otherwise, <span title="set the value">set their value</span> with <var>new
 value</var> "bold".
 
-<dd><strong>State</strong>: True if every [[text]] node that is
-<span>effectively contained</span> in the [[range]] has <span>effective
+<dd><strong>State</strong>: True if every <span>editable</span> [[text]] node
+that is <span>effectively contained</span> in the [[range]] has <span>effective
 value</span> at least 700.  Otherwise false.
 <!-- For bold and similar commands, IE 9 RC seems to consider the state true or
 false depending on the first element.  All other browsers follow the same
@@ -1727,8 +1769,8 @@
 Otherwise, <span title="set the value">set their value</span> with <var>new
 value</var> "italic".
 
-<dd><strong>State</strong>: True if every [[text]] node that is
-<span>effectively contained</span> in the [[range]] has <span>effective
+<dd><strong>State</strong>: True if every <span>editable</span> [[text]] node
+that is <span>effectively contained</span> in the [[range]] has <span>effective
 value</span> either "italic" or "oblique".  Otherwise false.
 
 <dd><strong>Value</strong>: Always the empty string.
@@ -1870,8 +1912,8 @@
 
 <p class=XXX>Has all the same problems as underline.
 
-<dd><strong>State</strong>: True if every [[text]] node that is
-<span>effectively contained</span> in the [[range]] has <span>effective
+<dd><strong>State</strong>: True if every <span>editable</span> [[text]] node
+that is <span>effectively contained</span> in the [[range]] has <span>effective
 value</span> "line-through".  Otherwise false.
 
 <dd><strong>Value</strong>: Always the empty string.
@@ -1901,8 +1943,8 @@
 <span>set the value</span> of each returned [[node]] with <var>new value</var>
 "sub".
 
-<dd><strong>State</strong>: True if every [[text]] node that is
-<span>effectively contained</span> in the [[range]] has <span>effective
+<dd><strong>State</strong>: True if every <span>editable</span> [[text]] node
+that is <span>effectively contained</span> in the [[range]] has <span>effective
 value</span> "sub".  Otherwise false.
 
 <dd><strong>Value</strong>:
@@ -1920,8 +1962,8 @@
 <span>set the value</span> of each returned [[node]] with <var>new value</var>
 "super".
 
-<dd><strong>State</strong>: True if every [[text]] node that is
-<span>effectively contained</span> in the [[range]] has <span>effective
+<dd><strong>State</strong>: True if every <span>editable</span> [[text]] node
+that is <span>effectively contained</span> in the [[range]] has <span>effective
 value</span> "super".  Otherwise false.
 
 <dd><strong>Value</strong>:
@@ -1971,7 +2013,7 @@
   underline letter-by-letter, it probably will vary.  But sometimes when they
   underline a bunch of text at once it will also vary, if the algorithm decides
   to create multiple elements for whatever reason (like an intervening
-  unwrappable element).  This is unlikely to match user expectations.  There's
+  unwrappable node).  This is unlikely to match user expectations.  There's
   not much we can do about this without entirely revamping text-decoration, so
   we'll have to live with it.
 
@@ -1987,8 +2029,8 @@
 may be.
 </div>
 
-<dd><strong>State</strong>: True if every [[text]] node that is
-<span>effectively contained</span> in the [[range]] has <span>effective
+<dd><strong>State</strong>: True if every <span>editable</span> [[text]] node
+that is <span>effectively contained</span> in the [[range]] has <span>effective
 value</span> "underline".  Otherwise false.
 
 <dd><strong>Value</strong>: Always the empty string.