Significantly improve whitespace handling
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Thu, 22 Sep 2011 14:03:58 -0600
changeset 618 e60077322f8a
parent 617 89fed05c54e0
child 619 031902d853b9
Significantly improve whitespace handling

This fixes a whole bunch of random bugs. See the data.js changes for
details. All tests added in the last commit should now give correct
results.

Fixes: http://www.w3.org/Bugs/Public/show_bug.cgi?id=14119
conformancetest/data.js
editing.html
implementation.js
source.html
--- a/conformancetest/data.js	Thu Sep 22 13:53:40 2011 -0600
+++ b/conformancetest/data.js	Thu Sep 22 14:03:58 2011 -0600
@@ -2129,11 +2129,11 @@
 	{"stylewithcss":[false,false,"",false,true,""],"delete":[false,false,"",false,false,""]}],
 ["foo &nbsp;[] bar",
 	[["stylewithcss","false"],["delete",""]],
-	"foo []bar",
+	"foo&nbsp;[] bar",
 	{"stylewithcss":[false,true,"",false,false,""],"delete":[false,false,"",false,false,""]}],
 ["foo &nbsp;[] bar",
 	[["stylewithcss","true"],["delete",""]],
-	"foo []bar",
+	"foo&nbsp;[] bar",
 	{"stylewithcss":[false,false,"",false,true,""],"delete":[false,false,"",false,false,""]}],
 ["foo &nbsp; []bar",
 	[["stylewithcss","false"],["delete",""]],
@@ -2153,11 +2153,11 @@
 	{"stylewithcss":[false,false,"",false,true,""],"delete":[false,false,"",false,false,""]}],
 ["foo <span>&nbsp;</span>[] bar",
 	[["stylewithcss","false"],["delete",""]],
-	"foo {}bar",
+	"foo&nbsp;{} bar",
 	{"stylewithcss":[false,true,"",false,false,""],"delete":[false,false,"",false,false,""]}],
 ["foo <span>&nbsp;</span>[] bar",
 	[["stylewithcss","true"],["delete",""]],
-	"foo {}bar",
+	"foo&nbsp;{} bar",
 	{"stylewithcss":[false,false,"",false,true,""],"delete":[false,false,"",false,false,""]}],
 ["foo <span>&nbsp;</span> []bar",
 	[["stylewithcss","false"],["delete",""]],
@@ -2201,11 +2201,11 @@
 	{"stylewithcss":[false,false,"",false,true,""],"delete":[false,false,"",false,false,""]}],
 ["<p>foo </p><p>[] bar</p>",
 	[["stylewithcss","false"],["delete",""]],
-	"<p>foo&nbsp;{}&nbsp;bar</p>",
+	"<p>foo{}bar</p>",
 	{"stylewithcss":[false,true,"",false,false,""],"delete":[false,false,"",false,false,""]}],
 ["<p>foo </p><p>[] bar</p>",
 	[["stylewithcss","true"],["delete",""]],
-	"<p>foo&nbsp;{}&nbsp;bar</p>",
+	"<p>foo{}bar</p>",
 	{"stylewithcss":[false,false,"",false,true,""],"delete":[false,false,"",false,false,""]}],
 ["foo<table><tr><td>[]bar</table>baz",
 	[["stylewithcss","false"],["delete",""]],
@@ -3905,11 +3905,11 @@
 	{"stylewithcss":[false,false,"",false,true,""],"delete":[false,false,"",false,false,""]}],
 ["foo<b> [&nbsp;bar]</b>",
 	[["stylewithcss","false"],["delete",""]],
-	"foo<b> []</b>",
+	"foo<b>&nbsp;[]</b>",
 	{"stylewithcss":[false,true,"",false,false,""],"delete":[false,false,"",false,false,""]}],
 ["foo<b> [&nbsp;bar]</b>",
 	[["stylewithcss","true"],["delete",""]],
-	"foo<b> []</b>",
+	"foo<b>&nbsp;[]</b>",
 	{"stylewithcss":[false,false,"",false,true,""],"delete":[false,false,"",false,false,""]}],
 ["<b>[foo&nbsp;] </b>bar",
 	[["stylewithcss","false"],["delete",""]],
@@ -8305,11 +8305,11 @@
 	{"stylewithcss":[false,false,"",false,true,""],"forwarddelete":[false,false,"",false,false,""]}],
 ["foo []&nbsp; bar",
 	[["stylewithcss","false"],["forwarddelete",""]],
-	"foo []bar",
+	"foo&nbsp;[] bar",
 	{"stylewithcss":[false,true,"",false,false,""],"forwarddelete":[false,false,"",false,false,""]}],
 ["foo []&nbsp; bar",
 	[["stylewithcss","true"],["forwarddelete",""]],
-	"foo []bar",
+	"foo&nbsp;[] bar",
 	{"stylewithcss":[false,false,"",false,true,""],"forwarddelete":[false,false,"",false,false,""]}],
 ["foo &nbsp;[] bar",
 	[["stylewithcss","false"],["forwarddelete",""]],
@@ -8329,11 +8329,11 @@
 	{"stylewithcss":[false,false,"",false,true,""],"forwarddelete":[false,false,"",false,false,""]}],
 ["foo []<span>&nbsp;</span> bar",
 	[["stylewithcss","false"],["forwarddelete",""]],
-	"foo {}bar",
+	"foo&nbsp;{} bar",
 	{"stylewithcss":[false,true,"",false,false,""],"forwarddelete":[false,false,"",false,false,""]}],
 ["foo []<span>&nbsp;</span> bar",
 	[["stylewithcss","true"],["forwarddelete",""]],
-	"foo {}bar",
+	"foo&nbsp;{} bar",
 	{"stylewithcss":[false,false,"",false,true,""],"forwarddelete":[false,false,"",false,false,""]}],
 ["foo <span>&nbsp;</span>[] bar",
 	[["stylewithcss","false"],["forwarddelete",""]],
@@ -17017,19 +17017,19 @@
 	{"stylewithcss":[false,false,"",false,true,""],"inserttext":[false,false,"",false,false,""]}],
 ["foo[] ",
 	[["stylewithcss","false"],["inserttext"," "]],
-	"foo []&nbsp;",
+	"foo&nbsp;[]",
 	{"stylewithcss":[false,true,"",false,false,""],"inserttext":[false,false,"",false,false,""]}],
 ["foo[] ",
 	[["stylewithcss","true"],["inserttext"," "]],
-	"foo []&nbsp;",
+	"foo&nbsp;[]",
 	{"stylewithcss":[false,false,"",false,true,""],"inserttext":[false,false,"",false,false,""]}],
 [" foo   []   ",
 	[["stylewithcss","false"],["inserttext"," "]],
-	" foo &nbsp;[]&nbsp;",
+	" foo&nbsp;[]",
 	{"stylewithcss":[false,true,"",false,false,""],"inserttext":[false,false,"",false,false,""]}],
 [" foo   []   ",
 	[["stylewithcss","true"],["inserttext"," "]],
-	" foo &nbsp;[]&nbsp;",
+	" foo&nbsp;[]",
 	{"stylewithcss":[false,false,"",false,true,""],"inserttext":[false,false,"",false,false,""]}],
 ["foo[]<span> </span>",
 	[["stylewithcss","false"],["inserttext"," "]],
@@ -17049,19 +17049,19 @@
 	{"stylewithcss":[false,false,"",false,true,""],"inserttext":[false,false,"",false,false,""]}],
 [" []foo",
 	[["stylewithcss","false"],["inserttext"," "]],
-	"&nbsp; []foo",
+	"&nbsp;[]foo",
 	{"stylewithcss":[false,true,"",false,false,""],"inserttext":[false,false,"",false,false,""]}],
 [" []foo",
 	[["stylewithcss","true"],["inserttext"," "]],
-	"&nbsp; []foo",
+	"&nbsp;[]foo",
 	{"stylewithcss":[false,false,"",false,true,""],"inserttext":[false,false,"",false,false,""]}],
 ["   []   foo ",
 	[["stylewithcss","false"],["inserttext"," "]],
-	"&nbsp;&nbsp;[] foo ",
+	"&nbsp;[]foo ",
 	{"stylewithcss":[false,true,"",false,false,""],"inserttext":[false,false,"",false,false,""]}],
 ["   []   foo ",
 	[["stylewithcss","true"],["inserttext"," "]],
-	"&nbsp;&nbsp;[] foo ",
+	"&nbsp;[]foo ",
 	{"stylewithcss":[false,false,"",false,true,""],"inserttext":[false,false,"",false,false,""]}],
 ["<span> </span>[]foo",
 	[["stylewithcss","false"],["inserttext"," "]],
--- a/editing.html	Thu Sep 22 13:53:40 2011 -0600
+++ b/editing.html	Thu Sep 22 14:03:58 2011 -0600
@@ -4662,6 +4662,12 @@
   <li>If the <a href=#active-range>active range</a> is null, abort these steps and do
   nothing.
 
+  <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at 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>.
+
+  <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at 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-end title=concept-range-end>end</a>.
+
   <li>Let <var title="">start node</var>, <var title="">start offset</var>, <var title="">end node</var>,
   and <var title="">end offset</var> be 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>
   and <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>nodes</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offsets</a>.
@@ -4881,7 +4887,7 @@
     &minus; <var title="">start offset</var>)</a></code> on <var title="">start node</var>.
 
     <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at (<var title="">start node</var>,
-    <var title="">start offset</var>).
+    <var title="">start offset</var>), with <var title="">fix collapsed space</var> false.
 
     <li>If <var title="">direction</var> is "forward", call <code class=external data-anolis-spec=domrange title=dom-Selection-collapseToStart><a href=http://html5.org/specs/dom-range.html#dom-selection-collapsetostart>collapseToStart()</a></code> on the
     <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
@@ -4957,10 +4963,10 @@
   <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.
 
   <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at 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-range-start title=concept-range-start>start</a>, with <var title="">fix collapsed space</var> false.
 
   <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at 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-end title=concept-range-end>end</a>.
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>, with <var title="">fix collapsed space</var> false.
 
   <li>
   <div class=comments>
@@ -5403,7 +5409,8 @@
 </ol>
 
 <p>To <dfn id=canonicalize-whitespace>canonicalize whitespace</dfn> at (<var title="">node</var>,
-<var title="">offset</var>):
+<var title="">offset</var>), given an optional boolean argument <var title="">fix collapsed
+space</var> that defaults to true:
 
 <ol>
   <li>If <var title="">node</var> is neither <a href=#editable>editable</a> nor an <a href=#editing-host>editing
@@ -5444,14 +5451,19 @@
   </ol>
 
   <li>
-  <p class=comments>Now we collapse any consecutive spaces.
+  <p class=comments>Now we collapse any consecutive spaces, if <var title="">fix
+  collapsed space</var> is true.
 
   <p>Let <var title="">end node</var> equal <var title="">start node</var> and <var title="">end
   offset</var> equal <var title="">start offset</var>.
 
   <li>Let <var title="">length</var> equal zero.
 
-  <li>Let <var title="">follows space</var> be false.
+  <li>
+  <p class=comments>This tries to delete spaces at the beginning of a line (<a href="http://www.w3.org/Bugs/Public/show_bug.cgi?id=14119">bug 14119</a>).
+
+  <p>Let <var title="">collapse spaces</var> be true if <var title="">start offset</var> is zero
+  and <var title="">start node</var> <a href=#follows-a-line-break>follows a line break</a>, otherwise false.
 
   <li>Repeat the following steps:
 
@@ -5478,14 +5490,15 @@
     space (0x0020) or non-breaking space (0x00A0):
 
     <ol>
-      <li>If <var title="">follows space</var> is true and the <var title="">end offset</var>th
-      <a href=http://dev.w3.org/2006/webapi/WebIDL/#dfn-code-unit>code unit</a> of <var title="">end node</var>'s <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> is a space (0x0020), call
+      <li>If <var title="">fix collapsed space</var> is true, and <var title="">collapse
+      spaces</var> is true, and the <var title="">end offset</var>th <a href=http://dev.w3.org/2006/webapi/WebIDL/#dfn-code-unit>code unit</a> of
+      <var title="">end node</var>'s <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> is a space (0x0020): 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="">end offset</var>, 1)</a></code> on <var title="">end node</var>, then
       continue this loop from the beginning.
 
-      <li>Set <var title="">follows space</var> to true if the <var title="">end offset</var>th
-      <a href=http://dev.w3.org/2006/webapi/WebIDL/#dfn-code-unit>code unit</a> of <var title="">end node</var>'s <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> is a space (0x0020), false
-      otherwise.
+      <li>Set <var title="">collapse spaces</var> to true if the <var title="">end offset</var>th
+      <a href=http://dev.w3.org/2006/webapi/WebIDL/#dfn-code-unit>code unit</a> of <var title="">end node</var>'s <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> is a space (0x0020),
+      false otherwise.
 
       <li>Add one to <var title="">end offset</var>.
 
@@ -5496,6 +5509,44 @@
   </ol>
 
   <li>
+  <p class=comments>We've already stripped leading whitespace, and collapsed
+  consecutive spaces.  Now we try to strip any collapsed trailing whitespace
+  (<a href="http://www.w3.org/Bugs/Public/show_bug.cgi?id=14119">bug 14119</a>
+  again).
+
+  <p>If <var title="">fix collapsed space</var> is true, then while (<var title="">start
+  node</var>, <var title="">start 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="">end node</var>,
+  <var title="">end offset</var>):
+
+  <ol>
+    <li>If <var title="">end node</var> has a <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> <a href=#in-the-same-editing-host>in the same editing
+    host</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">end offset</var> &minus; 1, set <var title="">end
+    node</var> to that <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>, then set <var title="">end offset</var> to <var title="">end
+    node</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a>.
+
+    <li>Otherwise, if <var title="">end offset</var> is zero and <var title="">end 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 <a href=#in-the-same-editing-host>in the same editing host</a>, set <var title="">end
+    offset</var> to <var title="">end node</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>, 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>Otherwise, 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> node 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>'s <a href=http://dev.w3.org/csswg/cssom/#resolved-value>resolved value</a> for "white-space" is neither "pre" nor "pre-wrap"
+    and <var title="">end offset</var> is <var title="">end node</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> and the last
+    <a href=http://dev.w3.org/2006/webapi/WebIDL/#dfn-code-unit>code unit</a> of <var title="">end node</var>'s <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> is a space (0x0020) and
+    <var title="">end node</var> <a href=#precedes-a-line-break>precedes a line break</a>:
+
+    <ol>
+      <li>Subtract one from <var title="">end offset</var>.
+
+      <li>Subtract one from <var title="">length</var>.
+
+      <li>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="">end offset</var>, 1)</a></code> on <var title="">end node</var>.
+    </ol>
+
+    <li>Otherwise, break from this loop.
+  </ol>
+
+  <li>
   <p class=comments>Finally we replace with the canonical sequence.
 
   <p>Let <var title="">replacement whitespace</var> be the <a href=#canonical-space-sequence>canonical space
@@ -6845,8 +6896,8 @@
   <p class=comments>Needed so that if there are multiple consecutive spaces we
   backspace over all at once.
 
-  <p><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at (<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>, <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-offset title=concept-boundary-point-offset>offset</a>).
+  <p><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at 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>.
 
   <li>Let <var title="">node</var> and <var title="">offset</var> be 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> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
@@ -7578,8 +7629,8 @@
   <li>If the <a href=#active-range>active range</a> is not <code class=external data-anolis-spec=domrange title=dom-Range-collapsed><a href=http://html5.org/specs/dom-range.html#dom-range-collapsed>collapsed</a></code>, <a href=#delete-the-selection>delete the selection</a>
   and abort these steps.
 
-  <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at (<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>, <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-offset title=concept-boundary-point-offset>offset</a>).
+  <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at 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>.
 
   <li>Let <var title="">node</var> and <var title="">offset</var> be 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> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
@@ -8787,19 +8838,18 @@
   and that <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=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node, set <var title="">node</var> to that <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>,
   then set <var title="">offset</var> to zero.
 
-  <li>
-  <p class=comments><var title="">value</var> may change back to a space when we
-  canonicalize, even if this step is triggered.
-
-  <p>If <var title="">value</var> is a space (U+0020), and either <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> whose <a href=http://dev.w3.org/csswg/cssom/#resolved-value>resolved value</a> for "white-space" is neither "pre" nor
-  "pre-wrap" or <var title="">node</var> is not 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> but its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is an
-  <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> whose <a href=http://dev.w3.org/csswg/cssom/#resolved-value>resolved value</a> for "white-space" is neither "pre" nor
-  "pre-wrap", set <var title="">value</var> to a non-breaking space (U+00A0).
-
   <li><a href=#record-current-overrides>Record current overrides</a>, and let <var title="">overrides</var> be
   the result.
 
+  <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+
+  <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at (<var title="">node</var>,
+  <var title="">offset</var>).
+
+  <li>Let (<var title="">node</var>, <var title="">offset</var>) be 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>.
+
   <li>If <var title="">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> node:
 
   <ol>
@@ -8838,10 +8888,10 @@
   <li><a href=#restore-states-and-values>Restore states and values</a> from <var title="">overrides</var>.
 
   <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at 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-range-start title=concept-range-start>start</a>, with <var title="">fix collapsed space</var> false.
 
   <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at 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-end title=concept-range-end>end</a>.
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>, with <var title="">fix collapsed space</var> false.
 
   <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapseToEnd><a href=http://html5.org/specs/dom-range.html#dom-selection-collapsetoend>collapseToEnd()</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
 </ol>
--- a/implementation.js	Thu Sep 22 13:53:40 2011 -0600
+++ b/implementation.js	Thu Sep 22 14:03:58 2011 -0600
@@ -4373,6 +4373,12 @@
 		return;
 	}
 
+	// "Canonicalize whitespace at the active range's start."
+	canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);
+
+	// "Canonicalize whitespace at the active range's end."
+	canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset);
+
 	// "Let start node, start offset, end node, and end offset be the active
 	// range's start and end nodes and offsets."
 	var startNode = getActiveRange().startContainer;
@@ -4560,8 +4566,9 @@
 		// node."
 		startNode.deleteData(startOffset, endOffset - startOffset);
 
-		// "Canonicalize whitespace at (start node, start offset)."
-		canonicalizeWhitespace(startNode, startOffset);
+		// "Canonicalize whitespace at (start node, start offset), with fix
+		// collapsed space false."
+		canonicalizeWhitespace(startNode, startOffset, false);
 
 		// "If direction is "forward", call collapseToStart() on the context
 		// object's Selection."
@@ -4646,11 +4653,13 @@
 		endNode.deleteData(0, endOffset);
 	}
 
-	// "Canonicalize whitespace at the active range's start."
-	canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);
-
-	// "Canonicalize whitespace at the active range's end."
-	canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset);
+	// "Canonicalize whitespace at the active range's start, with fix collapsed
+	// space false."
+	canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset, false);
+
+	// "Canonicalize whitespace at the active range's end, with fix collapsed
+	// space false."
+	canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset, false);
 
 	// "If block merging is false, or start block or end block is null, or
 	// start block is not in the same editing host as end block, or start block
@@ -5098,7 +5107,13 @@
 	return buffer;
 }
 
-function canonicalizeWhitespace(node, offset) {
+function canonicalizeWhitespace(node, offset, fixCollapsedSpace) {
+	if (fixCollapsedSpace === undefined) {
+		// "an optional boolean argument fix collapsed space that defaults to
+		// true"
+		fixCollapsedSpace = true;
+	}
+
 	// "If node is neither editable nor an editing host, abort these steps."
 	if (!isEditable(node) && !isEditingHost(node)) {
 		return;
@@ -5152,8 +5167,9 @@
 	// "Let length equal zero."
 	var length = 0;
 
-	// "Let follows space be false."
-	var followsSpace = false;
+	// "Let collapse spaces be true if start offset is zero and start node
+	// follows a line break, otherwise false."
+	var collapseSpaces = startOffset == 0 && followsLineBreak(startNode);
 
 	// "Repeat the following steps:"
 	while (true) {
@@ -5182,18 +5198,20 @@
 		&& ["pre", "pre-wrap"].indexOf(getComputedStyle(endNode.parentNode).whiteSpace) == -1
 		&& endOffset != getNodeLength(endNode)
 		&& /[ \xa0]/.test(endNode.data[endOffset])) {
-			// "If follows space is true and the end offsetth element of end
-			// node's data is a space (0x0020), call deleteData(end offset, 1)
-			// on end node, then continue this loop from the beginning."
-			if (followsSpace
+			// "If fix collapsed space is true, and collapse spaces is true,
+			// and the end offsetth code unit of end node's data is a space
+			// (0x0020): call deleteData(end offset, 1) on end node, then
+			// continue this loop from the beginning."
+			if (fixCollapsedSpace
+			&& collapseSpaces
 			&& " " == endNode.data[endOffset]) {
 				endNode.deleteData(endOffset, 1);
 				continue;
 			}
 
-			// "Set follows space to true if the end offsetth element of end
+			// "Set collapse spaces to true if the end offsetth element of end
 			// node's data is a space (0x0020), false otherwise."
-			followsSpace = " " == endNode.data[endOffset];
+			collapseSpaces = " " == endNode.data[endOffset];
 
 			// "Add one to end offset."
 			endOffset++;
@@ -5207,6 +5225,52 @@
 		}
 	}
 
+	// "If fix collapsed space is true, then while (start node, start offset)
+	// is before (end node, end offset):"
+	if (fixCollapsedSpace) {
+		while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {
+			// "If end node has a child in the same editing host with index end
+			// offset − 1, set end node to that child, then set end offset to end
+			// node's length."
+			if (0 <= endOffset - 1
+			&& endOffset - 1 < endNode.childNodes.length
+			&& inSameEditingHost(endNode, endNode.childNodes[endOffset - 1])) {
+				endNode = endNode.childNodes[endOffset - 1];
+				endOffset = getNodeLength(endNode);
+
+			// "Otherwise, if end offset is zero and end node's parent is in the
+			// same editing host, set end offset to end node's index, then set end
+			// node to its parent."
+			} else if (endOffset == 0
+			&& inSameEditingHost(endNode, endNode.parentNode)) {
+				endOffset = getNodeIndex(endNode);
+				endNode = endNode.parentNode;
+
+			// "Otherwise, if end node is a Text node and its parent's resolved
+			// value for "white-space" is neither "pre" nor "pre-wrap" and end
+			// offset is end node's length and the last code unit of end node's
+			// data is a space (0x0020) and end node precedes a line break:"
+			} else if (endNode.nodeType == Node.TEXT_NODE
+			&& ["pre", "pre-wrap"].indexOf(getComputedStyle(endNode.parentNode).whiteSpace) == -1
+			&& endOffset == getNodeLength(endNode)
+			&& endNode.data[endNode.data.length - 1] == " "
+			&& precedesLineBreak(endNode)) {
+				// "Subtract one from end offset."
+				endOffset--;
+
+				// "Subtract one from length."
+				length--;
+
+				// "Call deleteData(end offset, 1) on end node."
+				endNode.deleteData(endOffset, 1);
+
+			// "Otherwise, break from this loop."
+			} else {
+				break;
+			}
+		}
+	}
+
 	// "Let replacement whitespace be the canonical space sequence of length
 	// length. non-breaking start is true if start offset is zero and start
 	// node follows a line break, and false otherwise. non-breaking end is true
@@ -5886,8 +5950,7 @@
 			return;
 		}
 
-		// "Canonicalize whitespace at (active range's start node, active
-		// range's start offset)."
+		// "Canonicalize whitespace at the active range's start."
 		canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);
 
 		// "Let node and offset be the active range's start node and offset."
@@ -6498,8 +6561,7 @@
 			return;
 		}
 
-		// "Canonicalize whitespace at (active range's start node, active
-		// range's start offset)."
+		// "Canonicalize whitespace at the active range's start."
 		canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);
 
 		// "Let node and offset be the active range's start node and offset."
@@ -7506,21 +7568,21 @@
 			offset = 0;
 		}
 
-		// "If value is a space (U+0020), and either node is an Element whose
-		// resolved value for "white-space" is neither "pre" nor "pre-wrap" or
-		// node is not an Element but its parent is an Element whose resolved
-		// value for "white-space" is neither "pre" nor "pre-wrap", set value
-		// to a non-breaking space (U+00A0)."
-		var refElement = node.nodeType == Node.ELEMENT_NODE ? node : node.parentNode;
-		if (value == " "
-		&& refElement.nodeType == Node.ELEMENT_NODE
-		&& ["pre", "pre-wrap"].indexOf(getComputedStyle(refElement).whiteSpace) == -1) {
-			value = "\xa0";
-		}
-
 		// "Record current overrides, and let overrides be the result."
 		var overrides = recordCurrentOverrides();
 
+		// "Call collapse(node, offset) on the context object's Selection."
+		getSelection().collapse(node, offset);
+		getActiveRange().setStart(node, offset);
+		getActiveRange().setEnd(node, offset);
+
+		// "Canonicalize whitespace at (node, offset)."
+		canonicalizeWhitespace(node, offset);
+
+		// "Let (node, offset) be the active range's start."
+		node = getActiveRange().startContainer;
+		offset = getActiveRange().startOffset;
+
 		// "If node is a Text node:"
 		if (node.nodeType == Node.TEXT_NODE) {
 			// "Call insertData(offset, value) on node."
@@ -7532,7 +7594,10 @@
 
 			// "Call extend(node, offset + 1) on the context object's
 			// Selection."
-			getSelection().extend(node, offset + 1);
+			//
+			// Work around WebKit bug: the extend() can throw if the text we're
+			// adding is trailing whitespace.
+			try { getSelection().extend(node, offset + 1); } catch(e) {}
 			getActiveRange().setEnd(node, offset + 1);
 
 		// "Otherwise:"
@@ -7566,11 +7631,13 @@
 		// "Restore states and values from overrides."
 		restoreStatesAndValues(overrides);
 
-		// "Canonicalize whitespace at the active range's start."
-		canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);
-
-		// "Canonicalize whitespace at the active range's end."
-		canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset);
+		// "Canonicalize whitespace at the active range's start, with fix
+		// collapsed space false."
+		canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset, false);
+
+		// "Canonicalize whitespace at the active range's end, with fix
+		// collapsed space false."
+		canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset, false);
 
 		// "Call collapseToEnd() on the context object's Selection."
 		getSelection().collapseToEnd();
--- a/source.html	Thu Sep 22 13:53:40 2011 -0600
+++ b/source.html	Thu Sep 22 14:03:58 2011 -0600
@@ -4693,6 +4693,12 @@
   <li>If the <span>active range</span> is null, abort these steps and do
   nothing.
 
+  <li><span>Canonicalize whitespace</span> at the <span>active range</span>'s
+  [[rangestart]].
+
+  <li><span>Canonicalize whitespace</span> at the <span>active range</span>'s
+  [[rangeend]].
+
   <li>Let <var>start node</var>, <var>start offset</var>, <var>end node</var>,
   and <var>end offset</var> be the <span>active range</span>'s [[rangestart]]
   and [[rangeend]] [[bpnodes]] and [[bpoffsets]].
@@ -4913,7 +4919,7 @@
     &minus; <var>start offset</var>]] on <var>start node</var>.
 
     <li><span>Canonicalize whitespace</span> at (<var>start node</var>,
-    <var>start offset</var>).
+    <var>start offset</var>), with <var>fix collapsed space</var> false.
 
     <li>If <var>direction</var> is "forward", call [[collapsetostart]] on the
     [[contextobject]]'s [[selection]].
@@ -4989,10 +4995,10 @@
   [[deletedata|0, <var>end offset</var>]] on it.
 
   <li><span>Canonicalize whitespace</span> at the <span>active range</span>'s
-  [[rangestart]].
+  [[rangestart]], with <var>fix collapsed space</var> false.
 
   <li><span>Canonicalize whitespace</span> at the <span>active range</span>'s
-  [[rangeend]].
+  [[rangeend]], with <var>fix collapsed space</var> false.
 
   <li>
   <div class=comments>
@@ -5441,7 +5447,8 @@
 </ol>
 
 <p>To <dfn>canonicalize whitespace</dfn> at (<var>node</var>,
-<var>offset</var>):
+<var>offset</var>), given an optional boolean argument <var>fix collapsed
+space</var> that defaults to true:
 
 <ol>
   <li>If <var>node</var> is neither <span>editable</span> nor an <span>editing
@@ -5482,14 +5489,20 @@
   </ol>
 
   <li>
-  <p class=comments>Now we collapse any consecutive spaces.
+  <p class=comments>Now we collapse any consecutive spaces, if <var>fix
+  collapsed space</var> is true.
 
   <p>Let <var>end node</var> equal <var>start node</var> and <var>end
   offset</var> equal <var>start offset</var>.
 
   <li>Let <var>length</var> equal zero.
 
-  <li>Let <var>follows space</var> be false.
+  <li>
+  <p class=comments>This tries to delete spaces at the beginning of a line (<a
+  href=http://www.w3.org/Bugs/Public/show_bug.cgi?id=14119>bug 14119</a>).
+
+  <p>Let <var>collapse spaces</var> be true if <var>start offset</var> is zero
+  and <var>start node</var> <span>follows a line break</span>, otherwise false.
 
   <li>Repeat the following steps:
 
@@ -5516,14 +5529,15 @@
     space (0x0020) or non-breaking space (0x00A0):
 
     <ol>
-      <li>If <var>follows space</var> is true and the <var>end offset</var>th
-      [[codeunit]] of <var>end node</var>'s [[cddata]] is a space (0x0020), call
+      <li>If <var>fix collapsed space</var> is true, and <var>collapse
+      spaces</var> is true, and the <var>end offset</var>th [[codeunit]] of
+      <var>end node</var>'s [[cddata]] is a space (0x0020): call
       [[deletedata|<var>end offset</var>, 1]] on <var>end node</var>, then
       continue this loop from the beginning.
 
-      <li>Set <var>follows space</var> to true if the <var>end offset</var>th
-      [[codeunit]] of <var>end node</var>'s [[cddata]] is a space (0x0020), false
-      otherwise.
+      <li>Set <var>collapse spaces</var> to true if the <var>end offset</var>th
+      [[codeunit]] of <var>end node</var>'s [[cddata]] is a space (0x0020),
+      false otherwise.
 
       <li>Add one to <var>end offset</var>.
 
@@ -5534,6 +5548,44 @@
   </ol>
 
   <li>
+  <p class=comments>We've already stripped leading whitespace, and collapsed
+  consecutive spaces.  Now we try to strip any collapsed trailing whitespace
+  (<a href=http://www.w3.org/Bugs/Public/show_bug.cgi?id=14119>bug 14119</a>
+  again).
+
+  <p>If <var>fix collapsed space</var> is true, then while (<var>start
+  node</var>, <var>start offset</var>) is [[bpbefore]] (<var>end node</var>,
+  <var>end offset</var>):
+
+  <ol>
+    <li>If <var>end node</var> has a [[child]] <span>in the same editing
+    host</span> with [[index]] <var>end offset</var> &minus; 1, set <var>end
+    node</var> to that [[child]], then set <var>end offset</var> to <var>end
+    node</var>'s [[length]].
+
+    <li>Otherwise, if <var>end offset</var> is zero and <var>end node</var>'s
+    [[parent]] is <span>in the same editing host</span>, set <var>end
+    offset</var> to <var>end node</var>'s [[index]], then set <var>end
+    node</var> to its [[parent]].
+
+    <li>Otherwise, if <var>end node</var> is a [[text]] node and its
+    [[parent]]'s [[resval]] for "white-space" is neither "pre" nor "pre-wrap"
+    and <var>end offset</var> is <var>end node</var>'s [[length]] and the last
+    [[codeunit]] of <var>end node</var>'s [[cddata]] is a space (0x0020) and
+    <var>end node</var> <span>precedes a line break</span>:
+
+    <ol>
+      <li>Subtract one from <var>end offset</var>.
+
+      <li>Subtract one from <var>length</var>.
+
+      <li>Call [[deletedata|<var>end offset</var>, 1]] on <var>end node</var>.
+    </ol>
+
+    <li>Otherwise, break from this loop.
+  </ol>
+
+  <li>
   <p class=comments>Finally we replace with the canonical sequence.
 
   <p>Let <var>replacement whitespace</var> be the <span>canonical space
@@ -6903,8 +6955,8 @@
   <p class=comments>Needed so that if there are multiple consecutive spaces we
   backspace over all at once.
 
-  <p><span>Canonicalize whitespace</span> at (<span>active range</span>'s
-  [[startnode]], <span>active range</span>'s [[startoffset]]).
+  <p><span>Canonicalize whitespace</span> at the <span>active range</span>'s
+  [[rangestart]].
 
   <li>Let <var>node</var> and <var>offset</var> be the <span>active
   range</span>'s [[rangestart]] [[bpnode]] and [[bpoffset]].
@@ -7643,8 +7695,8 @@
   title=dom-Range-collapsed>collapsed</code>, <span>delete the selection</span>
   and abort these steps.
 
-  <li><span>Canonicalize whitespace</span> at (<span>active range</span>'s
-  [[startnode]], <span>active range</span>'s [[startoffset]]).
+  <li><span>Canonicalize whitespace</span> at the <span>active range</span>'s
+  [[rangestart]].
 
   <li>Let <var>node</var> and <var>offset</var> be the <span>active
   range</span>'s [[rangestart]] [[bpnode]] and [[bpoffset]].
@@ -8861,19 +8913,18 @@
   and that [[child]] is a [[text]] node, set <var>node</var> to that [[child]],
   then set <var>offset</var> to zero.
 
-  <li>
-  <p class=comments><var>value</var> may change back to a space when we
-  canonicalize, even if this step is triggered.
-
-  <p>If <var>value</var> is a space (U+0020), and either <var>node</var> is an
-  [[element]] whose [[resval]] for "white-space" is neither "pre" nor
-  "pre-wrap" or <var>node</var> is not an [[element]] but its [[parent]] is an
-  [[element]] whose [[resval]] for "white-space" is neither "pre" nor
-  "pre-wrap", set <var>value</var> to a non-breaking space (U+00A0).
-
   <li><span>Record current overrides</span>, and let <var>overrides</var> be
   the result.
 
+  <li>Call [[selcollapse|<var>node</var>, <var>offset</var>]] on the
+  [[contextobject]]'s [[selection]].
+
+  <li><span>Canonicalize whitespace</span> at (<var>node</var>,
+  <var>offset</var>).
+
+  <li>Let (<var>node</var>, <var>offset</var>) be the <span>active
+  range</span>'s [[rangestart]].
+
   <li>If <var>node</var> is a [[text]] node:
 
   <ol>
@@ -8914,10 +8965,10 @@
   <li><span>Restore states and values</span> from <var>overrides</var>.
 
   <li><span>Canonicalize whitespace</span> at the <span>active range</span>'s
-  [[rangestart]].
+  [[rangestart]], with <var>fix collapsed space</var> false.
 
   <li><span>Canonicalize whitespace</span> at the <span>active range</span>'s
-  [[rangeend]].
+  [[rangeend]], with <var>fix collapsed space</var> false.
 
   <li>Call [[collapsetoend]] on the [[contextobject]]'s [[selection]].
 </ol>