Experimentally rewrite definition of enabled
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Thu, 07 Jul 2011 13:32:42 -0600
changeset 367 8d6c424b650c
parent 366 0ee7984f8909
child 368 1f8c4dd6e377
Experimentally rewrite definition of enabled

Also add lots of editability checks to make sure the new definition is
consistent with behavior.
editcommands.html
implementation.js
source.html
--- a/editcommands.html	Tue Jul 05 15:56:37 2011 -0600
+++ b/editcommands.html	Thu Jul 07 13:32:42 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-5-july-2011>Work in Progress &mdash; Last Update 5 July 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-7-july-2011>Work in Progress &mdash; Last Update 7 July 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;ayg+spec@aryeh.name&gt;
@@ -449,10 +449,12 @@
 those listed in <a href=#miscellaneous-commands>Miscellaneous commands</a> are
 always <a href=#enabled>enabled</a>.  The other <a href=#command title=command>commands</a>
 defined here are <a href=#enabled>enabled</a> if the <a href=#active-range>active range</a> is not
-null, and are not <a href=#enabled>enabled</a> if it is.
-
-<p class=XXX>Not clear that this definition is right.  It doesn't match any
-browser.  Needs more testing and thought.
+null, and either there is some <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> <a href=#effectively-contained>effectively contained</a> in
+it that is an <a href=#editing-host>editing host</a> or the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-descendant title=concept-tree-descendant>descendant</a> of an
+<a href=#editing-host>editing host</a>, or its <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> or <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</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> is an
+<a href=#editing-host>editing host</a> or the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-descendant title=concept-tree-descendant>descendant</a> of an <a href=#editing-host>editing
+host</a>.
+
 <!--
 Testing with bold and formatBlock:
 
@@ -462,18 +464,25 @@
 descends from something editable).
 
 Firefox 6.0a2 seems to always return true if there's anything editable on the
-page, and throw otherwise.  Opera 11.11 seems to always return true if there's
-anything editable on the page, and false otherwise.  Neither behavior makes
-much sense.
+page, and throw otherwise.
 
 Chrome 14 dev seems to do something like return true if every node effectively
-contained in the selection is editable, false otherwise.  This makes the most
-sense, but it's too narrow for us, because we take some action in many cases
-even if there's some non-editable stuff around.
-
-What we really want this to do is return true if we'd do something when you run
-the command, false otherwise.  But that's impractically complicated for little
-benefit.  Some approximation closer to IE or Chrome is likely in order.
+contained in the selection is editable, false otherwise.
+
+Opera 11.11 seems to always return true if there's anything editable on the
+page, and false otherwise.
+
+Firefox and Opera behave more or less uselessly.  Chrome is not ideal because
+we want commands to be enabled if they'll have any effect at all, and basically
+all commands have an effect if there are some non-editable nodes effectively
+contained in the selection as I've specced it currently.  IE is conservative,
+in that it will return true in some cases even if running the command will have
+no effect, but it's pretty simple and it will only return false if there's no
+way the command will have an effect.
+
+Thus I go with IE.  Note that at least createLink and unlink can have an effect
+if the entire selection is non-editable, if something descends from an editable
+<a>, so we want to remain enabled in that case.
 -->
 
 
@@ -1405,6 +1414,9 @@
 <ol>
   <li>Let <var title="">command</var> be the current <a href=#command>command</a>.
 
+  <li>If <var title="">element</var> is not <a href=#editable>editable</a>, return the empty
+  list.
+
   <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
@@ -2132,9 +2144,9 @@
   <li><a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>, and let <var title="">node
   list</var> be the result.
 
-  <li>For each <code class=external data-anolis-spec=html title="the a element"><a href=http://www.whatwg.org/html/#the-a-element>a</a></code> element that has an <code class=external data-anolis-spec=html title=attr-hyperlink-href><a href=http://www.whatwg.org/html/#attr-hyperlink-href>href</a></code> attribute and is 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 some <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> in <var title="">node list</var>, set that element's
-  <code class=external data-anolis-spec=html title=attr-hyperlink-href><a href=http://www.whatwg.org/html/#attr-hyperlink-href>href</a></code> attribute to <var title="">value</var>.
+  <li>For each <a href=#editable>editable</a> <code class=external data-anolis-spec=html title="the a element"><a href=http://www.whatwg.org/html/#the-a-element>a</a></code> element that has an <code class=external data-anolis-spec=html title=attr-hyperlink-href><a href=http://www.whatwg.org/html/#attr-hyperlink-href>href</a></code>
+  attribute and is 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 some <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> in <var title="">node list</var>,
+  set that element's <code class=external data-anolis-spec=html title=attr-hyperlink-href><a href=http://www.whatwg.org/html/#attr-hyperlink-href>href</a></code> attribute to <var title="">value</var>.
   <!-- There are three approaches here.  For instance, if you ask browsers to
   create a link to "http://example.org" on the "b" here:
 
@@ -2649,13 +2661,14 @@
   list</var> be the result.
 
   <li>For each <var title="">node</var> in <var title="">node list</var>, unset the <code class=external data-anolis-spec=html title="the style attribute"><a href=http://www.whatwg.org/html/#the-style-attribute>style</a></code>
-  attribute of <var title="">node</var> (if it's 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 then all its
+  attribute of <var title="">node</var> if it's 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>,
+  then unset the <code class=external data-anolis-spec=html title="the style attribute"><a href=http://www.whatwg.org/html/#the-style-attribute>style</a></code> attribute of all its <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> <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-descendant title=concept-tree-descendant>descendants</a>.
 
-  <li>Let <var title="">elements to remove</var> be a list of all <a href=#html-element title="HTML
-  element">HTML elements</a> that are the same as or <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-descendant title=concept-tree-descendant>descendants</a> of some
-  member of <var title="">node list</var> and have non-null <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>parents</a> and satisfy
-  (insert conditions here).
+  <li>Let <var title="">elements to remove</var> be a list of all <a href=#editable>editable</a>
+  <a href=#html-element title="HTML element">HTML elements</a> that are the same as or
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-descendant title=concept-tree-descendant>descendants</a> of some member of <var title="">node list</var> and have non-null
+  <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>parents</a> and satisfy (insert conditions here).
 
   <p class=XXX>The conditions are not so simple to define, because we want to
   include non-conforming elements, which HTML doesn't give content models.  If
@@ -5857,6 +5870,9 @@
   <!-- Chrome 14 dev and Opera 11.11 do this even if the value is empty.
   Firefox 5.0a2 throws an exception. -->
 
+  <li>If 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> is neither
+  <a href=#editable>editable</a> nor an <a href=#editing-host>editing host</a>, abort these steps.
+
   <li>Let <var title="">frag</var> be the result of calling <code class=external data-anolis-spec=domps title=dom-Range-createContextualFragment><a href=http://html5.org/specs/dom-parsing.html#dom-range-createcontextualfragment>createContextualFragment(<var title="">value</var>)</a></code>
   on the <a href=#active-range>active range</a>.
 
@@ -5916,6 +5932,9 @@
 
   <li><a href=#delete-the-contents>Delete the contents</a> of <var title="">range</var>.
 
+  <li>If 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> is neither
+  <a href=#editable>editable</a> nor an <a href=#editing-host>editing host</a>, abort these steps.
+
   <li>If <var title="">range</var>'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> is a <a href=#prohibited-paragraph-child>prohibited paragraph
   child</a> whose sole <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> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, and its <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-offset title=concept-boundary-point-offset>offset</a> is 0,
   remove its <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 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> from it.
@@ -5971,6 +5990,9 @@
 
   <p class=XXX>This might blow up non-contenteditable stuff.
 
+  <li>If 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> is neither
+  <a href=#editable>editable</a> nor an <a href=#editing-host>editing host</a>, abort these steps.
+
   <li>Let <var title="">hr</var> be the result of calling <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("hr")</a></code> on the
   <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>.
 
@@ -6020,6 +6042,9 @@
 <ol>
   <li><a href=#delete-the-contents>Delete the contents</a> of the <a href=#active-range>active range</a>.
 
+  <li>If 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> is neither
+  <a href=#editable>editable</a> nor an <a href=#editing-host>editing host</a>, abort these steps.
+
   <li>If 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> 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
   "br" is not an <a href=#allowed-child>allowed child</a> of it, abort these steps.
   <!-- script, xmp, table, . . . -->
@@ -6107,6 +6132,9 @@
 <ol>
   <li><a href=#delete-the-contents>Delete the contents</a> of the <a href=#active-range>active range</a>.
 
+  <li>If 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> is neither
+  <a href=#editable>editable</a> nor an <a href=#editing-host>editing host</a>, abort these steps.
+
   <li>Let <var title="">range</var> be the <a href=#active-range>active range</a>.
 
   <li>Let <var title="">node</var> and <var title="">offset</var> be <var title="">range</var>'s
@@ -6426,15 +6454,18 @@
 This is still a huge headache, though.
 -->
 
+<p class=XXX>This doesn't work well if the input contains things that aren't
+supposed to appear in HTML, like carriage returns or nulls.  Nor is it going to
+work well if the current cursor position is in between two halves of a non-BMP
+character.  This will result in unserializability.  The current spec disregards
+this, as Chrome 14 dev does.
+
 <ol>
   <li><a href=#delete-the-contents>Delete the contents</a> of the <a href=#active-range>active range</a>.
   <!-- Chrome 14 dev does this even if passed the empty string. -->
 
-  <p class=XXX>This doesn't work well if the input contains things that aren't
-  supposed to appear in HTML, like carriage returns or nulls.  Nor is it going
-  to work well if the current cursor position is in between two halves of a
-  non-BMP character.  This will result in unserializability.  The current spec
-  disregards this, as Chrome 14 dev does.
+  <li>If 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> is neither
+  <a href=#editable>editable</a> nor an <a href=#editing-host>editing host</a>, abort these steps.
 
   <li>If <var title="">value</var>'s <a href=http://es5.github.com/#x15.5.5.1>length</a> is greater than one:
 
--- a/implementation.js	Tue Jul 05 15:56:37 2011 -0600
+++ b/implementation.js	Thu Jul 07 13:32:42 2011 -0600
@@ -602,9 +602,23 @@
 	//
 	// "Among commands defined in this specification, those listed in
 	// Miscellaneous commands are always enabled. The other commands defined
-	// here are enabled if the active range is not null, and are not enabled if
-	// it is."
-	return getActiveRange() || ["copy", "cut", "paste", "selectall", "stylewithcss", "usecss"].indexOf(command) != -1;
+	// here are enabled if the active range is not null, and either there is
+	// some node effectively contained in it that is an editing host or the
+	// descendant of an editing host, or its start node or end node is an
+	// editing host or the descendant of an editing host."
+	if (["copy", "cut", "paste", "selectall", "stylewithcss", "usecss"].indexOf(command) != -1) {
+		return true;
+	}
+
+	return getActiveRange()
+	&& (getAllEffectivelyContainedNodes(getActiveRange(), function(node) {
+		return isEditingHost(node)
+			|| getAncestors(node).some(isEditingHost);
+	}).length
+	|| isEditingHost(getActiveRange().startContainer)
+	|| getAncestors(getActiveRange().startContainer).some(isEditingHost)
+	|| isEditingHost(getActiveRange().endContainer)
+	|| getAncestors(getActiveRange().endContainer).some(isEditingHost));
 }
 
 function myQueryCommandIndeterm(command, range) {
@@ -1941,6 +1955,11 @@
 //@{
 
 function clearValue(element, command) {
+	// "If element is not editable, return the empty list."
+	if (!isEditable(element)) {
+		return [];
+	}
+
 	// "If element's specified value for command is null, return the empty
 	// list."
 	if (getSpecifiedValue(element, command) === null) {
@@ -2574,19 +2593,21 @@
 		// "Decompose the active range, and let node list be the result."
 		var nodeList = decomposeRange(getActiveRange());
 
-		// "For each a element that has an href attribute and is an ancestor of
-		// some node in node list, set that element's href attribute to value."
-		for (var i = 0; i < nodeList.length; i++) {
-			var candidate = nodeList[i].parentNode;
-			while (candidate) {
-				if (isHtmlElement(candidate, "A")
-				&& candidate.hasAttribute("href")) {
-					candidate.setAttribute("href", value);
+		// "For each editable a element that has an href attribute and is an
+		// ancestor of some node in node list, set that element's href
+		// attribute to value."
+		//
+		// TODO: We don't actually do this in tree order, not that it matters
+		// unless you're spying with mutation events.
+		nodeList.forEach(function(node) {
+			getAncestors(node).forEach(function(ancestor) {
+				if (isEditable(ancestor)
+				&& isHtmlElement(ancestor, "a")
+				&& ancestor.hasAttribute("href")) {
+					ancestor.setAttribute("href", value);
 				}
-
-				candidate = candidate.parentNode;
-			}
-		}
+			});
+		});
 
 		// "Set the value of each node in node list to value."
 		for (var i = 0; i < nodeList.length; i++) {
@@ -2921,38 +2942,37 @@
 		// "Decompose the active range, and let node list be the result."
 		var nodeList = decomposeRange(getActiveRange());
 
-		// "For each node in node list, unset the style attribute of node (if
-		// it's an Element) and then all its Element descendants."
-		for (var i = 0; i < nodeList.length; i++) {
-			for (
-				var node = nodeList[i];
-				node != nextNodeDescendants(nodeList[i]);
-				node = nextNode(node)
-			) {
-				if (node.nodeType == Node.ELEMENT_NODE) {
-					node.removeAttribute("style");
-				}
+		// "For each node in node list, unset the style attribute of node if
+		// it's an editable Element, then unset the style attribute of all its
+		// editable Element descendants."
+		nodeList.forEach(function(node) {
+			if (isEditable(node)
+			&& node.nodeType == Node.ELEMENT_NODE) {
+				node.removeAttribute("style");
 			}
-		}
-
-		// "Let elements to remove be a list of all HTML elements that are the
-		// same as or descendants of some member of node list and have non-null
-		// parents and satisfy (insert conditions here)."
+			getDescendants(node).forEach(function(descendant) {
+				if (isEditable(descendant)
+				&& descendant.nodeType == Node.ELEMENT_NODE) {
+					descendant.removeAttribute("style");
+				}
+			});
+		});
+
+		// "Let elements to remove be a list of all editable HTML elements that
+		// are the same as or descendants of some member of node list and have
+		// non-null parents and satisfy (insert conditions here)."
 		var elementsToRemove = [];
-		for (var i = 0; i < nodeList.length; i++) {
-			for (
-				var node = nodeList[i];
-				node == nodeList[i] || isDescendant(node, nodeList[i]);
-				node = nextNode(node)
-			) {
-				if (isHtmlElement(node)
+		nodeList.forEach(function(node) {
+			elementsToRemove.push(node);
+			[].push.apply(elementsToRemove, getDescendants(node));
+		});
+		elementsToRemove = elementsToRemove.filter(function(node) {
+			return isEditable(node)
+				&& isHtmlElement(node)
 				&& node.parentNode
 				// FIXME: Extremely partial list for testing
-				&& ["A", "AUDIO", "BR", "DIV", "HR", "IMG", "P", "TD", "VIDEO", "WBR"].indexOf(node.tagName) == -1) {
-					elementsToRemove.push(node);
-				}
-			}
-		}
+				&& ["A", "AUDIO", "BR", "DIV", "HR", "IMG", "P", "TD", "VIDEO", "WBR"].indexOf(node.tagName) == -1;
+		});
 
 		// "For each element in elements to remove:"
 		for (var i = 0; i < elementsToRemove.length; i++) {
@@ -5951,6 +5971,13 @@
 		// "Run deleteContents() on the range."
 		range.deleteContents();
 
+		// "If the active range's start node is neither editable nor an editing
+		// host, abort these steps."
+		if (!isEditable(getActiveRange().startContainer)
+		&& !isEditingHost(getActiveRange().startContainer)) {
+			return;
+		}
+
 		// "Let hr be the result of calling createElement("hr") on the
 		// context object."
 		var hr = document.createElement("hr");
@@ -5983,6 +6010,13 @@
 		// "Delete the contents of the active range."
 		deleteContents(getActiveRange());
 
+		// "If the active range's start node is neither editable nor an editing
+		// host, abort these steps."
+		if (!isEditable(getActiveRange().startContainer)
+		&& !isEditingHost(getActiveRange().startContainer)) {
+			return;
+		}
+
 		// "Let frag be the result of calling createContextualFragment(value)
 		// on the active range."
 		var frag = getActiveRange().createContextualFragment(value);
@@ -6039,6 +6073,13 @@
 		// "Delete the contents of range."
 		deleteContents(range);
 
+		// "If the active range's start node is neither editable nor an editing
+		// host, abort these steps."
+		if (!isEditable(getActiveRange().startContainer)
+		&& !isEditingHost(getActiveRange().startContainer)) {
+			return;
+		}
+
 		// "If range's start node is a prohibited paragraph child whose sole
 		// child is a br, and its start offset is 0, remove its start node's
 		// child from it."
@@ -6086,6 +6127,13 @@
 		// "Delete the contents of the active range."
 		deleteContents(getActiveRange());
 
+		// "If the active range's start node is neither editable nor an editing
+		// host, abort these steps."
+		if (!isEditable(getActiveRange().startContainer)
+		&& !isEditingHost(getActiveRange().startContainer)) {
+			return;
+		}
+
 		// "If the active range's start node is an Element, and "br" is not an
 		// allowed child of it, abort these steps."
 		if (getActiveRange().startContainer.nodeType == Node.ELEMENT_NODE
@@ -6166,6 +6214,13 @@
 		// "Delete the contents of the active range."
 		deleteContents(getActiveRange());
 
+		// "If the active range's start node is neither editable nor an editing
+		// host, abort these steps."
+		if (!isEditable(getActiveRange().startContainer)
+		&& !isEditingHost(getActiveRange().startContainer)) {
+			return;
+		}
+
 		// "Let range be the active range."
 		var range = getActiveRange();
 
@@ -6443,6 +6498,13 @@
 		// "Delete the contents of the active range."
 		deleteContents(getActiveRange());
 
+		// "If the active range's start node is neither editable nor an editing
+		// host, abort these steps."
+		if (!isEditable(getActiveRange().startContainer)
+		&& !isEditingHost(getActiveRange().startContainer)) {
+			return;
+		}
+
 		// "If value's length is greater than one:"
 		if (value.length > 1) {
 			// "For each element el in value, take the action for the
--- a/source.html	Tue Jul 05 15:56:37 2011 -0600
+++ b/source.html	Thu Jul 07 13:32:42 2011 -0600
@@ -387,10 +387,12 @@
 those listed in <a href=#miscellaneous-commands>Miscellaneous commands</a> are
 always <span>enabled</span>.  The other <span title=command>commands</span>
 defined here are <span>enabled</span> if the <span>active range</span> is not
-null, and are not <span>enabled</span> if it is.
-
-<p class=XXX>Not clear that this definition is right.  It doesn't match any
-browser.  Needs more testing and thought.
+null, and either there is some [[node]] <span>effectively contained</span> in
+it that is an <span>editing host</span> or the [[descendant]] of an
+<span>editing host</span>, or its [[startnode]] or [[endnode]] is an
+<span>editing host</span> or the [[descendant]] of an <span>editing
+host</span>.
+
 <!--
 Testing with bold and formatBlock:
 
@@ -400,18 +402,25 @@
 descends from something editable).
 
 Firefox 6.0a2 seems to always return true if there's anything editable on the
-page, and throw otherwise.  Opera 11.11 seems to always return true if there's
-anything editable on the page, and false otherwise.  Neither behavior makes
-much sense.
+page, and throw otherwise.
 
 Chrome 14 dev seems to do something like return true if every node effectively
-contained in the selection is editable, false otherwise.  This makes the most
-sense, but it's too narrow for us, because we take some action in many cases
-even if there's some non-editable stuff around.
-
-What we really want this to do is return true if we'd do something when you run
-the command, false otherwise.  But that's impractically complicated for little
-benefit.  Some approximation closer to IE or Chrome is likely in order.
+contained in the selection is editable, false otherwise.
+
+Opera 11.11 seems to always return true if there's anything editable on the
+page, and false otherwise.
+
+Firefox and Opera behave more or less uselessly.  Chrome is not ideal because
+we want commands to be enabled if they'll have any effect at all, and basically
+all commands have an effect if there are some non-editable nodes effectively
+contained in the selection as I've specced it currently.  IE is conservative,
+in that it will return true in some cases even if running the command will have
+no effect, but it's pretty simple and it will only return false if there's no
+way the command will have an effect.
+
+Thus I go with IE.  Note that at least createLink and unlink can have an effect
+if the entire selection is non-editable, if something descends from an editable
+<a>, so we want to remain enabled in that case.
 -->
 <!-- @} -->
 
@@ -1363,6 +1372,9 @@
 <ol>
   <li>Let <var>command</var> be the current <span>command</span>.
 
+  <li>If <var>element</var> is not <span>editable</span>, return the empty
+  list.
+
   <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
@@ -2109,9 +2121,9 @@
   <li><span>Decompose</span> the <span>active range</span>, and let <var>node
   list</var> be the result.
 
-  <li>For each [[a]] element that has an [[href]] attribute and is an
-  [[ancestor]] of some [[node]] in <var>node list</var>, set that element's
-  [[href]] attribute to <var>value</var>.
+  <li>For each <span>editable</span> [[a]] element that has an [[href]]
+  attribute and is an [[ancestor]] of some [[node]] in <var>node list</var>,
+  set that element's [[href]] attribute to <var>value</var>.
   <!-- There are three approaches here.  For instance, if you ask browsers to
   create a link to "http://example.org" on the "b" here:
 
@@ -2626,13 +2638,14 @@
   list</var> be the result.
 
   <li>For each <var>node</var> in <var>node list</var>, unset the [[style]]
-  attribute of <var>node</var> (if it's an [[element]]) and then all its
+  attribute of <var>node</var> if it's an <span>editable</span> [[element]],
+  then unset the [[style]] attribute of all its <span>editable</span>
   [[element]] [[descendants]].
 
-  <li>Let <var>elements to remove</var> be a list of all <span title="HTML
-  element">HTML elements</span> that are the same as or [[descendants]] of some
-  member of <var>node list</var> and have non-null [[parents]] and satisfy
-  (insert conditions here).
+  <li>Let <var>elements to remove</var> be a list of all <span>editable</span>
+  <span title="HTML element">HTML elements</span> that are the same as or
+  [[descendants]] of some member of <var>node list</var> and have non-null
+  [[parents]] and satisfy (insert conditions here).
 
   <p class=XXX>The conditions are not so simple to define, because we want to
   include non-conforming elements, which HTML doesn't give content models.  If
@@ -5864,6 +5877,9 @@
   <!-- Chrome 14 dev and Opera 11.11 do this even if the value is empty.
   Firefox 5.0a2 throws an exception. -->
 
+  <li>If the <span>active range</span>'s [[startnode]] is neither
+  <span>editable</span> nor an <span>editing host</span>, abort these steps.
+
   <li>Let <var>frag</var> be the result of calling <code data-anolis-spec=domps
   title=dom-Range-createContextualFragment>createContextualFragment(<var>value</var>)</code>
   on the <span>active range</span>.
@@ -5924,6 +5940,9 @@
 
   <li><span>Delete the contents</span> of <var>range</var>.
 
+  <li>If the <span>active range</span>'s [[startnode]] is neither
+  <span>editable</span> nor an <span>editing host</span>, abort these steps.
+
   <li>If <var>range</var>'s [[startnode]] is a <span>prohibited paragraph
   child</span> whose sole [[child]] is a [[br]], and its [[startoffset]] is 0,
   remove its [[startnode]]'s [[child]] from it.
@@ -5980,6 +5999,9 @@
 
   <p class=XXX>This might blow up non-contenteditable stuff.
 
+  <li>If the <span>active range</span>'s [[startnode]] is neither
+  <span>editable</span> nor an <span>editing host</span>, abort these steps.
+
   <li>Let <var>hr</var> be the result of calling <code
   data-anolis-spec=domcore
   title=dom-Document-createElement>createElement("hr")</code> on the
@@ -6035,6 +6057,9 @@
 <ol>
   <li><span>Delete the contents</span> of the <span>active range</span>.
 
+  <li>If the <span>active range</span>'s [[startnode]] is neither
+  <span>editable</span> nor an <span>editing host</span>, abort these steps.
+
   <li>If the <span>active range</span>'s [[startnode]] is an [[element]], and
   "br" is not an <span>allowed child</span> of it, abort these steps.
   <!-- script, xmp, table, . . . -->
@@ -6122,6 +6147,9 @@
 <ol>
   <li><span>Delete the contents</span> of the <span>active range</span>.
 
+  <li>If the <span>active range</span>'s [[startnode]] is neither
+  <span>editable</span> nor an <span>editing host</span>, abort these steps.
+
   <li>Let <var>range</var> be the <span>active range</span>.
 
   <li>Let <var>node</var> and <var>offset</var> be <var>range</var>'s
@@ -6444,15 +6472,18 @@
 This is still a huge headache, though.
 -->
 
+<p class=XXX>This doesn't work well if the input contains things that aren't
+supposed to appear in HTML, like carriage returns or nulls.  Nor is it going to
+work well if the current cursor position is in between two halves of a non-BMP
+character.  This will result in unserializability.  The current spec disregards
+this, as Chrome 14 dev does.
+
 <ol>
   <li><span>Delete the contents</span> of the <span>active range</span>.
   <!-- Chrome 14 dev does this even if passed the empty string. -->
 
-  <p class=XXX>This doesn't work well if the input contains things that aren't
-  supposed to appear in HTML, like carriage returns or nulls.  Nor is it going
-  to work well if the current cursor position is in between two halves of a
-  non-BMP character.  This will result in unserializability.  The current spec
-  disregards this, as Chrome 14 dev does.
+  <li>If the <span>active range</span>'s [[startnode]] is neither
+  <span>editable</span> nor an <span>editing host</span>, abort these steps.
 
   <li>If <var>value</var>'s [[strlen]] is greater than one: