Make delete work right for tables
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Mon, 06 Jun 2011 14:40:55 -0600
changeset 231 86474be03ddb
parent 230 4a024a59d8a2
child 232 ec4e7a39d45e
Make delete work right for tables

Now it won't generally delete <td>s or such, it will just delete the
contents.
autoimplementation.html
editcommands.html
implementation.js
preprocess
source.html
--- a/autoimplementation.html	Mon Jun 06 14:21:48 2011 -0600
+++ b/autoimplementation.html	Mon Jun 06 14:40:55 2011 -0600
@@ -305,6 +305,14 @@
 		'foo[]bar',
 		'<span>foo</span>{}<span>bar</span>',
 		'<span>foo[</span><span>]bar</span>',
+		'foo<span style=display:none>bar</span>[]baz',
+		'fo&ouml;[]bar',
+		'foo&#x308;[]bar',
+		'<p>foo<p>[]bar',
+		'<div>foo</div><div>[]bar</div>',
+		'<pre>foo</pre>[]bar',
+		'foo<br>[]bar',
+
 		'foo[bar]baz',
 
 		'foo<b>[bar]</b>baz',
@@ -347,6 +355,18 @@
 		'foo[<div>]bar<p>baz</p></div>',
 		'<div><p>foo</p>bar[</div>]baz',
 		'<div>foo<p>bar[</p></div>]baz',
+
+		'<table><tbody><tr><th>foo<th>[bar]<th>baz<tr><td>quz<td>qoz<td>qiz</table>',
+		'<table><tbody><tr><th>foo<th>ba[r<th>b]az<tr><td>quz<td>qoz<td>qiz</table>',
+		'<table><tbody><tr><th>fo[o<th>bar<th>b]az<tr><td>quz<td>qoz<td>qiz</table>',
+		'<table><tbody><tr><th>foo<th>bar<th>ba[z<tr><td>q]uz<td>qoz<td>qiz</table>',
+		'<table><tbody><tr><th>[foo<th>bar<th>baz]<tr><td>quz<td>qoz<td>qiz</table>',
+		'<table><tbody><tr><th>[foo<th>bar<th>baz<tr><td>quz<td>qoz<td>qiz]</table>',
+		'{<table><tbody><tr><th>foo<th>bar<th>baz<tr><td>quz<td>qoz<td>qiz</table>}',
+		'<table><tbody><tr><td>foo<td>ba[r<tr><td>baz<td>quz<tr><td>q]oz<td>qiz</table>',
+		'<p>fo[o<table><tr><td>b]ar</table><p>baz',
+		'<p>foo<table><tr><td>ba[r</table><p>b]az',
+		'<p>fo[o<table><tr><td>bar</table><p>b]az',
 	],
 	fontname: [
 		'foo[]bar',
--- a/editcommands.html	Mon Jun 06 14:21:48 2011 -0600
+++ b/editcommands.html	Mon Jun 06 14:40:55 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-3-june-2011>Work in Progress &mdash; Last Update 3 June 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-6-june-2011>Work in Progress &mdash; Last Update 6 June 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;ayg+spec@aryeh.name&gt;
@@ -933,14 +933,16 @@
 
 <p>To <dfn id=delete-the-selection>delete the selection</dfn>:
 
-<p class=XXX>This might blow up non-editable stuff.  It also might leave a
-block element empty, when we really need to add 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> <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> no matter
-what so it doesn't collapse.
-
 <p class=XXX>I'm uncertain about the use of prohibited paragraph children here.
 I'm using it mostly because it's convenient and seems relatively sensible.  If
 we really want to use it, we probably want to change its name.
 
+<p class=XXX>This treats selections in elements the same as selections in
+nearby text nodes, if there are any.  This might not be desired in all cases.
+Needs research into what sorts of selections the user can create.  E.g.,
+Gecko treats it differently if you select all the text in a table vs. selecting
+the table itself.
+
 <ol>
   <li>Let <var title="">range</var> be the <a href=#active-range>active range</a>.
 
@@ -1030,11 +1032,17 @@
   </ol>
 
   <li>If (<var title="">end node</var>, <var title="">end offset</var>) is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-bp-before title=concept-bp-before>before</a> (<var title="">start
-  node</var>, <var title="">start offset</var>), call <code class=external data-anolis-spec=domrange title=dom-Range-deleteContents><a href=http://html5.org/specs/dom-range.html#dom-range-deletecontents>deleteContents()</a></code> on <var title="">range</var>
-  and abort these steps.
-  <!-- This actually will have no effect other than collapsing the selection to
-  whatever point deleteContents() likes to collapse it to.  It might make more
-  sense to collapse to the start or end, though. -->
+  node</var>, <var title="">start offset</var>), set <var title="">range</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> to
+  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> and abort these steps.
+
+  <li>If <var title="">start node</var> is 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> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node and <var title="">start
+  offset</var> is 0, 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 <var title="">start
+  node</var>, then set <var title="">start node</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>.
+
+  <li>If <var title="">end node</var> is 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> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node and <var title="">end
+  offset</var> is its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a>, 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 <var title="">end node</var>, then set <var title="">end node</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>.
 
   <li>Set <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> to (<var title="">start node</var>,
   <var title="">start offset</var>) and its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> to (<var title="">end node</var>,
@@ -1043,18 +1051,80 @@
   <li>Let <var title="">start block</var> be the <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> of
   <var title="">range</var>.
 
-  <li>While <var title="">start block</var> is not a <a href=#prohibited-paragraph-child>prohibited paragraph
-  child</a> and 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 <a href=#in-the-same-editing-host>in the same editing host</a>, set
-  <var title="">start block</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>.
+  <li>While <var title="">start block</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 <a href=#in-the-same-editing-host>in the same editing
+  host</a>, and <var title="">start block</var> is not a <a href=#prohibited-paragraph-child>prohibited paragraph
+  child</a> or "span" is not an <a href=#allowed-child>allowed child</a> of <var title="">start
+  block</var>, set <var title="">start block</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>.
 
   <li>Let <var title="">end block</var> be the <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> of
   <var title="">range</var>.
 
-  <li>While <var title="">end block</var> is not a <a href=#prohibited-paragraph-child>prohibited paragraph
-  child</a> and 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 <a href=#in-the-same-editing-host>in the same editing host</a>, set
-  <var title="">end block</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>.
-
-  <li>Call <code class=external data-anolis-spec=domrange title=dom-Range-deleteContents><a href=http://html5.org/specs/dom-range.html#dom-range-deletecontents>deleteContents()</a></code> on <var title="">range</var>.
+  <li>While <var title="">end block</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 <a href=#in-the-same-editing-host>in the same editing
+  host</a>, and <var title="">end block</var> is not a <a href=#prohibited-paragraph-child>prohibited paragraph
+  child</a> or "span" is not an <a href=#allowed-child>allowed child</a> of <var title="">end
+  block</var> or <var title="">end block</var> is a <code class=external data-anolis-spec=html title="the td element"><a href=http://www.whatwg.org/html/#the-td-element>td</a></code> or <code class=external data-anolis-spec=html title="the th element"><a href=http://www.whatwg.org/html/#the-th-element>th</a></code>, set <var title="">end
+  block</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>.
+  <!--
+  We're willing to let start block be a td or th, but not end block.  This
+  means if the selection starts in a table cell and ends in some other block,
+  we'll merge the contents of the latter block into the cell, but not vice
+  versa.  This matches Chrome 13 dev and Opera 11.11.  Firefox 5.0a2 doesn't
+  allow merging either way, and in IE9 the delete key just does nothing.
+  -->
+
+  <!-- This is based on deleteData() in DOM Range. -->
+  <li>If <var title="">start node</var> and <var title="">end node</var> are the same, and
+  <var title="">start node</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#text>Text</a></code> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code>
+  node, call <code class=external data-anolis-spec=domcore title=dom-CharacterData-deleteData><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-deletedata>deleteData(<var title="">start offset</var>, <var title="">end offset</var>
+  &minus; <var title="">start offset</var>)</a></code> on <var title="">start node</var>.
+
+  <li>Otherwise:
+
+  <ol>
+    <li>If <var title="">start node</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#text>Text</a></code> or
+    <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node, call <code class=external data-anolis-spec=domcore title=dom-CharacterData-deleteData><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-deletedata>deleteData()</a></code> on it, with <var title="">start offset</var>
+    as the first argument and (<a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of <var title="">start node</var> &minus;
+    <var title="">start offset</var>) as the second argument.
+
+    <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="">range</var>, append
+    <var title="">node</var> to <var title="">node list</var> if the last member of <var title="">node
+    list</var> (if any) is not an <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of <var title="">node</var>;
+    <var title="">node</var> is <a href=#editable>editable</a>; and <var title="">node</var> is not a
+    <code class=external data-anolis-spec=html title="the thead element"><a href=http://www.whatwg.org/html/#the-thead-element>thead</a></code>, <code class=external data-anolis-spec=html title="the tbody element"><a href=http://www.whatwg.org/html/#the-tbody-element>tbody</a></code>, <code class=external data-anolis-spec=html title="the tfoot element"><a href=http://www.whatwg.org/html/#the-tfoot-element>tfoot</a></code>, <code class=external data-anolis-spec=html title="the tr element"><a href=http://www.whatwg.org/html/#the-tr-element>tr</a></code>, <code class=external data-anolis-spec=html title="the th element"><a href=http://www.whatwg.org/html/#the-th-element>th</a></code>, or <code class=external data-anolis-spec=html title="the td element"><a href=http://www.whatwg.org/html/#the-td-element>td</a></code>.
+    <!--
+    IE9 doesn't seem to let you do any intercell deletions: the delete key does
+    nothing if you select across multiple cells.  Firefox 5.0a2 and Opera 11.11
+    behave as the spec says, not removing any table things.  Chrome 13 dev will
+    remove entire rows if selected.  Note that IE, Firefox, Word 2007, and
+    OpenOffice.org 3.2.1 Ubuntu all switch to a magic cell-selection mode when
+    you try to select between cells, at least in some cases, instead of
+    selecting letter-by-letter.
+    -->
+
+    <li>For each <var title="">node</var> in <var title="">node list</var>:
+
+    <ol>
+      <li>Let <var title="">parent</var> be the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of <var title="">node</var>.
+
+      <li>Remove <var title="">node</var> from <var title="">parent</var>.
+
+      <li>While <var title="">parent</var> is an <a href=#editable>editable</a> <a href=#inline-node>inline
+      node</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> 0, let <var title="">grandparent</var> be the
+      <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of <var title="">parent</var>, then remove <var title="">parent</var> from
+      <var title="">grandparent</var>, then set <var title="">parent</var> to
+      <var title="">grandparent</var>.
+
+      <li>If <var title="">parent</var> is <a href=#editable>editable</a> or an <a href=#editing-host>editing
+      host</a>, is not an <a href=#inline-node>inline node</a>, and has no <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>,
+      call <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a> and append the
+      result as the last <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 <var title="">parent</var>.
+    </ol>
+
+    <li>If <var title="">end node</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#text>Text</a></code> or
+    <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node, call <code class=external data-anolis-spec=domcore title=dom-CharacterData-deleteData><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-deletedata>deleteData(0, <var title="">end offset</var>)</a></code> on it.
+  </ol>
 
   <!--
   Now we need to merge blocks.  The simplest case is something like
@@ -1077,43 +1147,29 @@
   where one descends from the other.
   -->
 
-  <!-- The deletions might have left some empty elements.  We want to remove
-  empty inline nodes, and add a <br> child to empty blocks.  We remove the
-  empty inline nodes first, and only add the <br>s later, because we don't want
-  to add the brs to blocks that we're about to merge. -->
-  <li>While <var title="">start node</var> is an <a href=#editable>editable</a> <a href=#inline-node>inline
-  node</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> 0, let <var title="">parent</var> be 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>,
-  then remove <var title="">start node</var> from <var title="">parent</var>, then set <var title="">start
-  node</var> to <var title="">parent</var>.
-
-  <li>While <var title="">end node</var> is an <a href=#editable>editable</a> <a href=#inline-node>inline
-  node</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> 0, let <var title="">parent</var> be 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>,
-  then remove <var title="">end node</var> from <var title="">parent</var>, then set <var title="">end
-  node</var> to <var title="">parent</var>.
-
   <li>If <var title="">start block</var> is not <a href=#in-the-same-editing-host>in the same editing host</a> as
   <var title="">end block</var>, or <var title="">start block</var> is neither an <a href=#editing-host>editing
   host</a> nor a <a href=#prohibited-paragraph-child>prohibited paragraph child</a>, or <var title="">end
   block</var> is neither an <a href=#editing-host>editing host</a> nor a <a href=#prohibited-paragraph-child>prohibited
-  paragraph child</a>, or <var title="">start block</var> and <var title="">end block</var>
-  are the same:
-  <!-- Then we don't try to merge anything. -->
-
-  <ol>
-    <li>If <var title="">start 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> with no <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, is
-    either <a href=#editable>editable</a> or an <a href=#editing-host>editing host</a>, and is not an
-    <a href=#inline-node>inline node</a>, call <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on the
-    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a> and append the result as the last <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 <var title="">start
-    node</var>.
-
-    <li>If <var title="">end 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> with no <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, is
-    either <a href=#editable>editable</a> or an <a href=#editing-host>editing host</a>, and is not an
-    <a href=#inline-node>inline node</a>, call <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on the
-    <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a> and append the result as the last <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 <var title="">end
-    node</var>.
-
-    <li>Abort these steps.
-  </ol>
+  paragraph child</a>, or "span" is not an <a href=#allowed-child>allowed child</a> of
+  <var title="">start block</var>, or "span" is not an <a href=#allowed-child>allowed child</a> of
+  <var title="">end block</var>, or <var title="">end block</var> is a <code class=external data-anolis-spec=html title="the td element"><a href=http://www.whatwg.org/html/#the-td-element>td</a></code> or <code class=external data-anolis-spec=html title="the th element"><a href=http://www.whatwg.org/html/#the-th-element>th</a></code>, or
+  <var title="">start block</var> and <var title="">end block</var> are the same, set
+  <var title="">range</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> to 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> and then abort these
+  steps.
+
+  <!--
+  We might have added a br to the start/end block in an earlier step.  Now
+  we're about to merge the blocks, and we don't want the br's to get in the
+  way.  The end block is being destroyed no matter what.  If the start block
+  winds up empty after merging, we'll add a new br child at the end so it
+  doesn't collapse.
+  -->
+  <li>If <var title="">start block</var> has one <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>, which 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>, remove
+  its <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.
+
+  <li>If <var title="">end block</var> has one <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>, which 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>, remove its
+  <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.
 
   <li>If <var title="">start block</var> 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 <var title="">end block</var>:
   <!-- Just repeatedly blow up the end block. -->
--- a/implementation.js	Mon Jun 06 14:21:48 2011 -0600
+++ b/implementation.js	Mon Jun 06 14:40:55 2011 -0600
@@ -855,17 +855,37 @@
 		endOffset = getNodeLength(referenceNode);
 	}
 
-	// "If (end node, end offset) is before (start node, start offset), call
-	// deleteContents() on range and abort these steps."
+	// "If (end node, end offset) is before (start node, start offset), set
+	// range's end to its start and abort these steps."
 	var startPoint = document.createRange();
 	startPoint.setStart(startNode, startOffset);
 	var endPoint = document.createRange();
 	endPoint.setStart(endNode, endOffset);
 	if (startPoint.compareBoundaryPoints(Range.START_TO_START, endPoint) == 1) {
-		range.deleteContents();
+		range.setEnd(range.startContainer, range.startOffset);
 		return;
 	}
 
+	// "If start node is a Text or Comment node and start offset is 0, set
+	// start offset to the index of start node, then set start node to its
+	// parent."
+	if ((startNode.nodeType == Node.TEXT_NODE
+	|| startNode.nodeType == Node.COMMENT_NODE)
+	&& startOffset == 0) {
+		startOffset = getNodeIndex(startNode);
+		startNode = startNode.parentNode;
+	}
+
+	// "If end node is a Text or Comment node and end offset is its length, set
+	// end offset to one plus the index of end node, then set end node to its
+	// parent."
+	if ((endNode.nodeType == Node.TEXT_NODE
+	|| endNode.nodeType == Node.COMMENT_NODE)
+	&& endOffset == getNodeLength(endNode)) {
+		endOffset = 1 + getNodeIndex(endNode);
+		endNode = endNode.parentNode;
+	}
+
 	// "Set range's start to (start node, start offset) and its end to (end
 	// node, end offset)."
 	range.setStart(startNode, startOffset);
@@ -874,82 +894,130 @@
 	// "Let start block be the start node of range."
 	var startBlock = range.startContainer;
 
-	// "While start block is not a prohibited paragraph child and its parent is
-	// in the same editing host, set start block to its parent."
-	while (!isProhibitedParagraphChild(startBlock)
-	&& inSameEditingHost(startBlock, startBlock.parentNode)) {
+	// "While start block's parent is in the same editing host, and start block
+	// is not a prohibited paragraph child or "span" is not an allowed child of
+	// start block, set start block to its parent."
+	while (inSameEditingHost(startBlock, startBlock.parentNode)
+	&& (!isProhibitedParagraphChild(startBlock)
+	|| !isAllowedChild("span", startBlock))) {
 		startBlock = startBlock.parentNode;
 	}
 
 	// "Let end block be the end node of range."
 	var endBlock = range.endContainer;
 
-	// "While end block is not a prohibited paragraph child and its parent is
-	// in the same editing host, set end block to its parent."
-	while (!isProhibitedParagraphChild(endBlock)
-	&& inSameEditingHost(endBlock, endBlock.parentNode)) {
+	// "While end block's parent is in the same editing host, and end block is
+	// not a prohibited paragraph child or "span" is not an allowed child of
+	// end block or end block is a td or th, set end block to its parent."
+	while (inSameEditingHost(endBlock, endBlock.parentNode)
+	&& (!isProhibitedParagraphChild(endBlock)
+	|| !isAllowedChild("span", endBlock)
+	|| isHtmlElement(endBlock, ["td", "th"]))) {
 		endBlock = endBlock.parentNode;
 	}
 
-	// "Call deleteContents() on range."
-	range.deleteContents();
-
-	// "While start node is an editable inline node with length 0, let parent
-	// be its parent, then remove start node from parent, then set start node
-	// to parent."
-	while (isEditable(startNode)
-	&& isInlineNode(startNode)
-	&& getNodeLength(startNode) == 0) {
-		var parent_ = startNode.parentNode;
-		parent_.removeChild(startNode);
-		startNode = parent_;
-	}
-
-	// "While end node is an editable inline node with length 0, let parent be
-	// its parent, then remove end node from parent, then set end node to
-	// parent."
-	while (isEditable(endNode)
-	&& isInlineNode(endNode)
-	&& getNodeLength(endNode) == 0) {
-		var parent_ = endNode.parentNode;
-		parent_.removeChild(endNode);
-		endNode = parent_;
+	// "If start node and end node are the same, and start node is an editable
+	// Text or Comment node, call deleteData(start offset, end offset − start
+	// offset) on start node."
+	if (startNode == endNode
+	&& isEditable(startNode)
+	&& (startNode.nodeType == Node.TEXT_NODE || startNode.nodeType == Node.COMMENT_NODE)) {
+		startNode.deleteData(startOffset, endOffset - startOffset);
+
+	// "Otherwise:"
+	} else {
+		// "If start node is an editable Text or Comment node, call
+		// deleteData() on it, with start offset as the first argument and
+		// (length of start node − start offset) as the second argument."
+		if (isEditable(startNode)
+		&& (startNode.nodeType == Node.TEXT_NODE
+		|| startNode.nodeType == Node.COMMENT_NODE)) {
+			startNode.deleteData(startOffset, getNodeLength(startNode) - startOffset);
+		}
+
+		// "Let node list be a list of nodes, initially empty."
+		//
+		// "For each node contained in range, append node to node list if the
+		// last member of node list (if any) is not an ancestor of node; node
+		// is editable; and node is not a thead, tbody, tfoot, tr, th, or td."
+		var nodeList = collectContainedNodes(range,
+			function(node) {
+				return isEditable(node)
+					&& !isHtmlElement(node, ["thead", "tbody", "tfoot", "tr", "th", "td"]);
+			}
+		);
+
+		// "For each node in node list:"
+		for (var i = 0; i < nodeList.length; i++) {
+			var node = nodeList[i];
+
+			// "Let parent be the parent of node."
+			var parent_ = node.parentNode;
+
+			// "Remove node from parent."
+			parent_.removeChild(node);
+
+			// "While parent is an editable inline node with length 0, let
+			// grandparent be the parent of parent, then remove parent from
+			// grandparent, then set parent to grandparent."
+			while (isEditable(parent_)
+			&& isInlineNode(parent_)
+			&& getNodeLength(parent_) == 0) {
+				var grandparent = parent_.parentNode;
+				grandparent.removeChild(parent_);
+				parent_ = grandparent;
+			}
+
+			// "If parent is editable or an editing host, is not an inline
+			// node, and has no children, call createElement("br") on the
+			// context object and append the result as the last child of
+			// parent."
+			if ((isEditable(parent_) || isEditingHost(parent_))
+			&& !isInlineNode(parent_)
+			&& !parent_.hasChildNodes()) {
+				parent_.appendChild(document.createElement("br"));
+			}
+		}
+
+		// "If end node is an editable Text or Comment node, call deleteData(0,
+		// end offset) on it."
+		if (isEditable(endNode)
+		&& (endNode.nodeType == Node.TEXT_NODE
+		|| endNode.nodeType == Node.COMMENT_NODE)) {
+			endNode.deleteData(0, endOffset);
+		}
 	}
 
 	// "If start block is not in the same editing host as end block, or start
 	// block is neither an editing host nor a prohibited paragraph child, or
 	// end block is neither an editing host nor a prohibited paragraph child,
-	// or start block and end block are the same:"
+	// or "span" is not an allowed child of start block, or "span" is not an
+	// allowed child of end block, or end block is a td or th, or start block
+	// and end block are the same, set range's end to its start and then abort
+	// these steps."
 	if (!inSameEditingHost(startBlock, endBlock)
 	|| (!isEditingHost(startBlock) && !isProhibitedParagraphChild(startBlock))
 	|| (!isEditingHost(endBlock) && !isProhibitedParagraphChild(endBlock))
+	|| !isAllowedChild("span", startBlock)
+	|| !isAllowedChild("span", endBlock)
+	|| isHtmlElement(endBlock, ["td", "th"])
 	|| startBlock == endBlock) {
-		// "If start node is an Element with no children, is either editable or
-		// an editing host, and is not an inline node, call createElement("br")
-		// on the context object and append the result as the last child of
-		// start node."
-		if ((isEditable(startNode) || isEditingHost(startNode))
-		&& startNode.nodeType == Node.ELEMENT_NODE
-		&& !startNode.hasChildNodes()
-		&& !isInlineNode(startNode)) {
-			startNode.appendChild(document.createElement("br"));
-		}
-
-		// "If end node is an Element with no children, is either editable or
-		// an editing host, and is not an inline node, call createElement("br")
-		// on the context object and append the result as the last child of end
-		// node."
-		if ((isEditable(endNode) || isEditingHost(endNode))
-		&& endNode.nodeType == Node.ELEMENT_NODE
-		&& !endNode.hasChildNodes()
-		&& !isInlineNode(endNode)) {
-			endNode.appendChild(document.createElement("br"));
-		}
-
-		// "Abort these steps."
+		range.setEnd(range.startContainer, range.startOffset);
 		return;
 	}
 
+	// "If start block has one child, which is a br, remove its child from it."
+	if (startBlock.children.length == 1
+	&& isHtmlElement(startBlock.firstChild, "br")) {
+		startBlock.removeChild(startBlock.firstChild);
+	}
+
+	// "If end block has one child, which is a br, remove its child from it."
+	if (endBlock.children.length == 1
+	&& isHtmlElement(endBlock.firstChild, "br")) {
+		endBlock.removeChild(endBlock.firstChild);
+	}
+
 	// "If start block is an ancestor of end block:"
 	if (isAncestor(startBlock, endBlock)) {
 		// "Let reference node be end block."
--- a/preprocess	Mon Jun 06 14:21:48 2011 -0600
+++ b/preprocess	Mon Jun 06 14:40:55 2011 -0600
@@ -84,6 +84,12 @@
     'style': '<code data-anolis-spec=html title="the style attribute">style</code>',
     'sub': '<code data-anolis-spec=html title="the sub and sup elements">sub</code>',
     'sup': '<code data-anolis-spec=html title="the sub and sup elements">sup</code>',
+    'tbody': '<code data-anolis-spec=html title="the tbody element">tbody</code>',
+    'td': '<code data-anolis-spec=html title="the td element">td</code>',
+    'tfoot': '<code data-anolis-spec=html title="the tfoot element">tfoot</code>',
+    'th': '<code data-anolis-spec=html title="the th element">th</code>',
+    'thead': '<code data-anolis-spec=html title="the thead element">thead</code>',
+    'tr': '<code data-anolis-spec=html title="the tr element">tr</code>',
     'text': '<code data-anolis-spec=domcore>Text</code>',
     'treeorder': '<span data-anolis-spec=domcore>tree order</span>',
     'u': '<code data-anolis-spec=html title="the u element">u</code>',
@@ -104,6 +110,7 @@
 
 fnreplace = {
     'createelement': '<code data-anolis-spec=domcore title=dom-Document-createElement>createElement(\\1)</code>',
+    'deletedata': '<code data-anolis-spec=domcore title=dom-CharacterData-deleteData>deleteData(\\1)</code>',
     'insertnode': '<code data-anolis-spec=domrange title=dom-Range-insertNode>insertNode(\\1)</code>',
 }
 
--- a/source.html	Mon Jun 06 14:21:48 2011 -0600
+++ b/source.html	Mon Jun 06 14:40:55 2011 -0600
@@ -893,14 +893,16 @@
 
 <p>To <dfn>delete the selection</dfn>:
 
-<p class=XXX>This might blow up non-editable stuff.  It also might leave a
-block element empty, when we really need to add a [[br]] [[child]] no matter
-what so it doesn't collapse.
-
 <p class=XXX>I'm uncertain about the use of prohibited paragraph children here.
 I'm using it mostly because it's convenient and seems relatively sensible.  If
 we really want to use it, we probably want to change its name.
 
+<p class=XXX>This treats selections in elements the same as selections in
+nearby text nodes, if there are any.  This might not be desired in all cases.
+Needs research into what sorts of selections the user can create.  E.g.,
+Gecko treats it differently if you select all the text in a table vs. selecting
+the table itself.
+
 <ol>
   <li>Let <var>range</var> be the <span>active range</span>.
 
@@ -991,12 +993,17 @@
 
   <li>If (<var>end node</var>, <var>end offset</var>) is <span
   data-anolis-spec=domrange title=concept-bp-before>before</span> (<var>start
-  node</var>, <var>start offset</var>), call <code data-anolis-spec=domrange
-  title=dom-Range-deleteContents>deleteContents()</code> on <var>range</var>
-  and abort these steps.
-  <!-- This actually will have no effect other than collapsing the selection to
-  whatever point deleteContents() likes to collapse it to.  It might make more
-  sense to collapse to the start or end, though. -->
+  node</var>, <var>start offset</var>), set <var>range</var>'s [[rangeend]] to
+  its [[rangestart]] and abort these steps.
+
+  <li>If <var>start node</var> is a [[text]] or [[comment]] node and <var>start
+  offset</var> is 0, set <var>start offset</var> to the [[index]] of <var>start
+  node</var>, then set <var>start node</var> to its [[parent]].
+
+  <li>If <var>end node</var> is a [[text]] or [[comment]] node and <var>end
+  offset</var> is its [[nodelength]], set <var>end offset</var> to one plus the
+  [[index]] of <var>end node</var>, then set <var>end node</var> to its
+  [[parent]].
 
   <li>Set <var>range</var>'s [[rangestart]] to (<var>start node</var>,
   <var>start offset</var>) and its [[rangeend]] to (<var>end node</var>,
@@ -1005,19 +1012,80 @@
   <li>Let <var>start block</var> be the [[rangestart]] [[bpnode]] of
   <var>range</var>.
 
-  <li>While <var>start block</var> is not a <span>prohibited paragraph
-  child</span> and its [[parent]] is <span>in the same editing host</span>, set
-  <var>start block</var> to its [[parent]].
+  <li>While <var>start block</var>'s [[parent]] is <span>in the same editing
+  host</span>, and <var>start block</var> is not a <span>prohibited paragraph
+  child</span> or "span" is not an <span>allowed child</span> of <var>start
+  block</var>, set <var>start block</var> to its [[parent]].
 
   <li>Let <var>end block</var> be the [[rangeend]] [[bpnode]] of
   <var>range</var>.
 
-  <li>While <var>end block</var> is not a <span>prohibited paragraph
-  child</span> and its [[parent]] is <span>in the same editing host</span>, set
-  <var>end block</var> to its [[parent]].
-
-  <li>Call <code data-anolis-spec=domrange
-  title=dom-Range-deleteContents>deleteContents()</code> on <var>range</var>.
+  <li>While <var>end block</var>'s [[parent]] is <span>in the same editing
+  host</span>, and <var>end block</var> is not a <span>prohibited paragraph
+  child</span> or "span" is not an <span>allowed child</span> of <var>end
+  block</var> or <var>end block</var> is a [[td]] or [[th]], set <var>end
+  block</var> to its [[parent]].
+  <!--
+  We're willing to let start block be a td or th, but not end block.  This
+  means if the selection starts in a table cell and ends in some other block,
+  we'll merge the contents of the latter block into the cell, but not vice
+  versa.  This matches Chrome 13 dev and Opera 11.11.  Firefox 5.0a2 doesn't
+  allow merging either way, and in IE9 the delete key just does nothing.
+  -->
+
+  <!-- This is based on deleteData() in DOM Range. -->
+  <li>If <var>start node</var> and <var>end node</var> are the same, and
+  <var>start node</var> is an <span>editable</span> [[text]] or [[comment]]
+  node, call [[deletedata|<var>start offset</var>, <var>end offset</var>
+  &minus; <var>start offset</var>]] on <var>start node</var>.
+
+  <li>Otherwise:
+
+  <ol>
+    <li>If <var>start node</var> is an <span>editable</span> [[text]] or
+    [[comment]] node, call [[deletedata|]] on it, with <var>start offset</var>
+    as the first argument and ([[nodelength]] of <var>start node</var> &minus;
+    <var>start offset</var>) as the second argument.
+
+    <li>Let <var>node list</var> be a list of [[nodes]], initially empty.
+
+    <li>For each <var>node</var> [[contained]] in <var>range</var>, append
+    <var>node</var> to <var>node list</var> if the last member of <var>node
+    list</var> (if any) is not an [[ancestor]] of <var>node</var>;
+    <var>node</var> is <span>editable</span>; and <var>node</var> is not a
+    [[thead]], [[tbody]], [[tfoot]], [[tr]], [[th]], or [[td]].
+    <!--
+    IE9 doesn't seem to let you do any intercell deletions: the delete key does
+    nothing if you select across multiple cells.  Firefox 5.0a2 and Opera 11.11
+    behave as the spec says, not removing any table things.  Chrome 13 dev will
+    remove entire rows if selected.  Note that IE, Firefox, Word 2007, and
+    OpenOffice.org 3.2.1 Ubuntu all switch to a magic cell-selection mode when
+    you try to select between cells, at least in some cases, instead of
+    selecting letter-by-letter.
+    -->
+
+    <li>For each <var>node</var> in <var>node list</var>:
+
+    <ol>
+      <li>Let <var>parent</var> be the [[parent]] of <var>node</var>.
+
+      <li>Remove <var>node</var> from <var>parent</var>.
+
+      <li>While <var>parent</var> is an <span>editable</span> <span>inline
+      node</span> with [[nodelength]] 0, let <var>grandparent</var> be the
+      [[parent]] of <var>parent</var>, then remove <var>parent</var> from
+      <var>grandparent</var>, then set <var>parent</var> to
+      <var>grandparent</var>.
+
+      <li>If <var>parent</var> is <span>editable</span> or an <span>editing
+      host</span>, is not an <span>inline node</span>, and has no [[children]],
+      call [[createelement|"br"]] on the [[contextobject]] and append the
+      result as the last [[child]] of <var>parent</var>.
+    </ol>
+
+    <li>If <var>end node</var> is an <span>editable</span> [[text]] or
+    [[comment]] node, call [[deletedata|0, <var>end offset</var>]] on it.
+  </ol>
 
   <!--
   Now we need to merge blocks.  The simplest case is something like
@@ -1040,43 +1108,29 @@
   where one descends from the other.
   -->
 
-  <!-- The deletions might have left some empty elements.  We want to remove
-  empty inline nodes, and add a <br> child to empty blocks.  We remove the
-  empty inline nodes first, and only add the <br>s later, because we don't want
-  to add the brs to blocks that we're about to merge. -->
-  <li>While <var>start node</var> is an <span>editable</span> <span>inline
-  node</span> with [[nodelength]] 0, let <var>parent</var> be its [[parent]],
-  then remove <var>start node</var> from <var>parent</var>, then set <var>start
-  node</var> to <var>parent</var>.
-
-  <li>While <var>end node</var> is an <span>editable</span> <span>inline
-  node</span> with [[nodelength]] 0, let <var>parent</var> be its [[parent]],
-  then remove <var>end node</var> from <var>parent</var>, then set <var>end
-  node</var> to <var>parent</var>.
-
   <li>If <var>start block</var> is not <span>in the same editing host</span> as
   <var>end block</var>, or <var>start block</var> is neither an <span>editing
   host</span> nor a <span>prohibited paragraph child</span>, or <var>end
   block</var> is neither an <span>editing host</span> nor a <span>prohibited
-  paragraph child</span>, or <var>start block</var> and <var>end block</var>
-  are the same:
-  <!-- Then we don't try to merge anything. -->
-
-  <ol>
-    <li>If <var>start node</var> is an [[element]] with no [[children]], is
-    either <span>editable</span> or an <span>editing host</span>, and is not an
-    <span>inline node</span>, call [[createelement|"br"]] on the
-    [[contextobject]] and append the result as the last [[child]] of <var>start
-    node</var>.
-
-    <li>If <var>end node</var> is an [[element]] with no [[children]], is
-    either <span>editable</span> or an <span>editing host</span>, and is not an
-    <span>inline node</span>, call [[createelement|"br"]] on the
-    [[contextobject]] and append the result as the last [[child]] of <var>end
-    node</var>.
-
-    <li>Abort these steps.
-  </ol>
+  paragraph child</span>, or "span" is not an <span>allowed child</span> of
+  <var>start block</var>, or "span" is not an <span>allowed child</span> of
+  <var>end block</var>, or <var>end block</var> is a [[td]] or [[th]], or
+  <var>start block</var> and <var>end block</var> are the same, set
+  <var>range</var>'s [[rangeend]] to its [[rangestart]] and then abort these
+  steps.
+
+  <!--
+  We might have added a br to the start/end block in an earlier step.  Now
+  we're about to merge the blocks, and we don't want the br's to get in the
+  way.  The end block is being destroyed no matter what.  If the start block
+  winds up empty after merging, we'll add a new br child at the end so it
+  doesn't collapse.
+  -->
+  <li>If <var>start block</var> has one [[child]], which is a [[br]], remove
+  its [[child]] from it.
+
+  <li>If <var>end block</var> has one [[child]], which is a [[br]], remove its
+  [[child]] from it.
 
   <li>If <var>start block</var> is an [[ancestor]] of <var>end block</var>:
   <!-- Just repeatedly blow up the end block. -->