Preliminary spec/implementation of insertText
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Sun, 19 Jun 2011 10:59:43 -0600
changeset 291 47b1dc8e4e8e
parent 290 443821c02635
child 292 0042250f7e87
Preliminary spec/implementation of insertText
editcommands.html
implementation.js
inserttexttest.html
manualtest.js
source.html
tests.js
--- a/editcommands.html	Sun Jun 19 10:41:54 2011 -0600
+++ b/editcommands.html	Sun Jun 19 10:59:43 2011 -0600
@@ -117,12 +117,13 @@
    <li><a href=#the-inserthorizontalrule-command><span class=secno>7.15 </span>The <code title="">insertHorizontalRule</code> command</a></li>
    <li><a href=#the-insertorderedlist-command><span class=secno>7.16 </span>The <code title="">insertOrderedList</code> command</a></li>
    <li><a href=#the-insertparagraph-command><span class=secno>7.17 </span>The <code title="">insertParagraph</code> command</a></li>
-   <li><a href=#the-insertunorderedlist-command><span class=secno>7.18 </span>The <code title="">insertUnorderedList</code> command</a></li>
-   <li><a href=#the-justifycenter-command><span class=secno>7.19 </span>The <code title="">justifyCenter</code> command</a></li>
-   <li><a href=#the-justifyfull-command><span class=secno>7.20 </span>The <code title="">justifyFull</code> command</a></li>
-   <li><a href=#the-justifyleft-command><span class=secno>7.21 </span>The <code title="">justifyLeft</code> command</a></li>
-   <li><a href=#the-justifyright-command><span class=secno>7.22 </span>The <code title="">justifyRight</code> command</a></li>
-   <li><a href=#the-outdent-command><span class=secno>7.23 </span>The <code title="">outdent</code> command</a></ol></li>
+   <li><a href=#the-inserttext-command><span class=secno>7.18 </span>The <code title="">insertText</code> command</a></li>
+   <li><a href=#the-insertunorderedlist-command><span class=secno>7.19 </span>The <code title="">insertUnorderedList</code> command</a></li>
+   <li><a href=#the-justifycenter-command><span class=secno>7.20 </span>The <code title="">justifyCenter</code> command</a></li>
+   <li><a href=#the-justifyfull-command><span class=secno>7.21 </span>The <code title="">justifyFull</code> command</a></li>
+   <li><a href=#the-justifyleft-command><span class=secno>7.22 </span>The <code title="">justifyLeft</code> command</a></li>
+   <li><a href=#the-justifyright-command><span class=secno>7.23 </span>The <code title="">justifyRight</code> command</a></li>
+   <li><a href=#the-outdent-command><span class=secno>7.24 </span>The <code title="">outdent</code> command</a></ol></li>
  <li><a href=#miscellaneous-commands><span class=secno>8 </span>Miscellaneous commands</a>
   <ol>
    <li><a href=#the-selectall-command><span class=secno>8.1 </span>The <code title="">selectAll</code> command</a></li>
@@ -5434,36 +5435,70 @@
 </ol>
 
 
-<h3 id=the-insertunorderedlist-command><span class=secno>7.18 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
+<h3 id=the-inserttext-command><span class=secno>7.18 </span><dfn>The <code title="">insertText</code> command</dfn></h3>
+
+<!--
+Supported only by WebKit.  Tests in other browsers were manual.  In the manual
+tests, where the value was always "a", IE9 and Firefox 5.0a2 match the spec
+exactly as far as I can tell; Chrome 14 dev and Opera 11.11 might match it in
+theory, but normalize the selection first, so they don't match it in practice.
+-->
+
+<p><a href=#action>Action</a>:
+
+<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. -->
+
+  <li>If <var title="">value</var> is the empty string, abort these steps.
+
+  <li>Let <var title="">text</var> be the result of calling <code class=external data-anolis-spec=domcore title=dom-Document-createTextNode><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createtextnode>createTextNode(<var title="">value</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>.
+
+  <p class=XXX>We should actually add to existing text nodes if present, just
+  to be tidy.
+
+  <p class=XXX>WebKit seems to strip newlines from programmatic input, and does
+  magic for tabs.
+
+  <li>Call <code class=external data-anolis-spec=domrange title=dom-Range-insertNode><a href=http://html5.org/specs/dom-range.html#dom-range-insertnode>insertNode(<var title="">text</var>)</a></code> on the <a href=#active-range>active range</a>.
+
+  <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="">text</var>, <var title="">length</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>, where <var title="">length</var> is the <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="">text</var>.
+</ol>
+
+
+<h3 id=the-insertunorderedlist-command><span class=secno>7.19 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
 <p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
 "ul".
 
 
-<h3 id=the-justifycenter-command><span class=secno>7.19 </span><dfn>The <code title="">justifyCenter</code> command</dfn></h3>
+<h3 id=the-justifycenter-command><span class=secno>7.20 </span><dfn>The <code title="">justifyCenter</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "center".
 
 
-<h3 id=the-justifyfull-command><span class=secno>7.20 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
+<h3 id=the-justifyfull-command><span class=secno>7.21 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "justify".
 
 
-<h3 id=the-justifyleft-command><span class=secno>7.21 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
+<h3 id=the-justifyleft-command><span class=secno>7.22 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "left".
 
 
-<h3 id=the-justifyright-command><span class=secno>7.22 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
+<h3 id=the-justifyright-command><span class=secno>7.23 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
 <var title="">alignment</var> "right".
 
 
-<h3 id=the-outdent-command><span class=secno>7.23 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
+<h3 id=the-outdent-command><span class=secno>7.24 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -5666,6 +5701,23 @@
 take the <a href=#action>action</a> for <a href=#the-delete-command>the <code title="">delete</code>
 command</a>.
 
+<p>When the user instructs the user agent to delete the next character inside
+an <a href=#editing-host>editing host</a>, such as by pressing the Delete key while the
+cursor is in an <a href=#editable>editable</a> <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>, the user agent must take the
+<a href=#action>action</a> for <a href=#the-forwarddelete-command>the <code title="">forwardDelete</code>
+command</a>.
+
+<p>When the user instructs the user agent to insert text inside an
+<a href=#editing-host>editing host</a>, such as by typing on the keyboard while the cursor
+is in an <a href=#editable>editable</a> <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>, the user agent must take the
+<a href=#action>action</a> for <a href=#the-inserttext-command>the <code title="">insertText</code> command</a>,
+with <var title="">value</var> equal to the text the user provided.  If the user inserts
+multiple characters at once or in quick succession, this specification does not
+define whether it is treated as one insertion or several consecutive
+insertions.  If the inserted text includes line breaks, the user agent must not
+process those as part of this paragraph, but instead according to the
+instructions above for processing line breaks.
+
 
 <h2 class=no-num id=acknowledgements>Acknowledgements</h2>
 <p>Thanks to:
--- a/implementation.js	Sun Jun 19 10:41:54 2011 -0600
+++ b/implementation.js	Sun Jun 19 10:59:43 2011 -0600
@@ -5588,6 +5588,33 @@
 };
 //@}
 
+///// The insertText command /////
+//@{
+commands.inserttext = {
+	action: function(value) {
+		// "Delete the contents of the active range."
+		deleteContents(getActiveRange());
+
+		// "If value is the empty string, abort these steps."
+		if (value == "") {
+			return;
+		}
+
+		// "Let text be the result of calling createTextNode(value) on the
+		// context object."
+		var text = document.createTextNode(value);
+
+		// "Call insertNode(text) on the active range."
+		getActiveRange().insertNode(text);
+
+		// "Call collapse(text, length) on the context object's Selection,
+		// where length is the length of text."
+		getActiveRange().setStart(text, text.length);
+		getActiveRange().setEnd(text, text.length);
+	}
+};
+//@}
+
 ///// The insertUnorderedList command /////
 commands.insertunorderedlist = {
 	// "Toggle lists with tag name "ul"."
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/inserttexttest.html	Sun Jun 19 10:59:43 2011 -0600
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Text insertion tests</title>
+<link rel=stylesheet href=tests.css>
+<p>Legend: {[ are the selection anchor, }] are the selection focus, {}
+represent an element boundary point, [] represent a text node boundary point.
+Syntax and some of the tests taken from <a
+href=http://www.browserscope.org/richtext2/test>Browserscope</a>.  data-start
+and data-end attributes also represent element boundary points, with the node
+being the element with the attribute and the offset given as the attribute
+value, for cases where HTML parsing doesn't allow text nodes.  Currently we
+don't really pay attention to reversed selections at all, so they might get
+displayed as forwards or such.
+
+<p><input type=button value="Clear cached results" onclick="clearCachedResults()">
+
+<div id=tests>
+	<input type=button value="Run tests" onclick="runTests()">
+	<table border=1><tr><th>Input<th>Spec<th>Browser<th>Same</table>
+	<p><label>New test input: <input></label> <input type=button value="Add test" onclick="addTest()">
+</div>
+
+<div id=overlay>Tap the A key repeatedly until this annoying message
+disappears!  (But not too quickly.  And don't hit any other keys or click with
+the mouse anywhere, it will mess it up.<span id=testcount>  <span></span>
+manual test(s) remain.</span>)</div>
+
+<script src=implementation.js></script>
+<script src=tests.js></script>
+<script>
+var command = "inserttext";
+var keyname = "textinsertion";
+</script>
+<script src=manualtest.js></script>
--- a/manualtest.js	Sun Jun 19 10:41:54 2011 -0600
+++ b/manualtest.js	Sun Jun 19 10:59:43 2011 -0600
@@ -1,4 +1,6 @@
-var tests = tests[command];
+// We don't support non-default values for manual tests, so only strings need
+// apply.
+var tests = tests[command].filter(function(test) { return typeof test == "string"});
 
 var testsRunning = false;
 
@@ -38,8 +40,16 @@
 	var tr = doSetup("#tests table", 0);
 	var input = document.querySelector("#tests label input");
 	var test = input.value;
+	if (command == "inserttext") {
+		// Yay hack
+		test = ["a", test];
+	}
 	doInputCell(tr, test);
 	doSpecCell(tr, test, command, false);
+	if (typeof test != "string") {
+		// Yay hack
+		test = test[1];
+	}
 	if (localStorage.getItem(keyname + "test-" + test) !== null) {
 		// Yay, I get to cheat.  Remove the overlay div so the user doesn't
 		// keep hitting the key, in case it takes a while.
--- a/source.html	Sun Jun 19 10:41:54 2011 -0600
+++ b/source.html	Sun Jun 19 10:59:43 2011 -0600
@@ -5454,6 +5454,42 @@
 </ol>
 <!-- @} -->
 
+<h3><dfn>The <code title>insertText</code> command</dfn></h3>
+<!-- @{ -->
+<!--
+Supported only by WebKit.  Tests in other browsers were manual.  In the manual
+tests, where the value was always "a", IE9 and Firefox 5.0a2 match the spec
+exactly as far as I can tell; Chrome 14 dev and Opera 11.11 might match it in
+theory, but normalize the selection first, so they don't match it in practice.
+-->
+
+<p><span>Action</span>:
+
+<ol>
+  <li><span>Delete the contents</span> of the <span>active range</span>.
+  <!-- Chrome 14 dev does this even if passed the empty string. -->
+
+  <li>If <var>value</var> is the empty string, abort these steps.
+
+  <li>Let <var>text</var> be the result of calling <code
+  data-anolis-spec=domcore
+  title=dom-Document-createTextNode>createTextNode(<var>value</var>)</code> on
+  the [[contextobject]].
+
+  <p class=XXX>We should actually add to existing text nodes if present, just
+  to be tidy.
+
+  <p class=XXX>WebKit seems to strip newlines from programmatic input, and does
+  magic for tabs.
+
+  <li>Call [[insertnode|<var>text</var>]] on the <span>active range</span>.
+
+  <li>Call [[selcollapse|<var>text</var>, <var>length</var>]] on the
+  [[contextobject]]'s [[selection]], where <var>length</var> is the [[length]]
+  of <var>text</var>.
+</ol>
+<!-- @} -->
+
 <h3><dfn>The <code title>insertUnorderedList</code> command</dfn></h3>
 <p><span>Action</span>: <span>Toggle lists</span> with <var>tag name</var>
 "ul".
@@ -5692,6 +5728,23 @@
 while the cursor is in an <span>editable</span> [[node]], the user agent must
 take the <span>action</span> for <span>the <code title>delete</code>
 command</span>.
+
+<p>When the user instructs the user agent to delete the next character inside
+an <span>editing host</span>, such as by pressing the Delete key while the
+cursor is in an <span>editable</span> [[node]], the user agent must take the
+<span>action</span> for <span>the <code title>forwardDelete</code>
+command</span>.
+
+<p>When the user instructs the user agent to insert text inside an
+<span>editing host</span>, such as by typing on the keyboard while the cursor
+is in an <span>editable</span> [[node]], the user agent must take the
+<span>action</span> for <span>the <code title>insertText</code> command</span>,
+with <var>value</var> equal to the text the user provided.  If the user inserts
+multiple characters at once or in quick succession, this specification does not
+define whether it is treated as one insertion or several consecutive
+insertions.  If the inserted text includes line breaks, the user agent must not
+process those as part of this paragraph, but instead according to the
+instructions above for processing line breaks.
 <!-- @} -->
 
 <h2 class=no-num>Acknowledgements</h2>
--- a/tests.js	Sun Jun 19 10:41:54 2011 -0600
+++ b/tests.js	Sun Jun 19 10:59:43 2011 -0600
@@ -1773,6 +1773,33 @@
 		'foo<a href=foo>[]bar</a>',
 	],
 	//@}
+	inserttext: [
+	//@{
+		'foo[bar]baz',
+		['', 'foo[bar]baz'],
+		['\t', 'foo[]bar'],
+		[' ', 'foo[]bar'],
+		['\n', 'foo[]bar'],
+		['\r', 'foo[]bar'],
+		['\r\n', 'foo[]bar'],
+		'foo[]bar',
+		'<p>foo[]',
+		'<p>foo</p>{}',
+		'<p>[]foo',
+		'<p>{}foo',
+		'{}<p>foo',
+		'<p>foo</p>{}<p>bar</p>',
+		'<b>foo[]</b>bar',
+		'<b>foo</b>[]bar',
+		'foo<b>{}</b>bar',
+		'<a>foo[]</a>bar',
+		'<a>foo</a>[]bar',
+		'<a href=/>foo[]</a>bar',
+		'<a href=/>foo</a>[]bar',
+		'<p>fo[o<p>b]ar',
+		'<p>fo[o<p>bar<p>b]az',
+	],
+	//@}
 	insertunorderedlist: [
 	//@{
 		'foo[]bar',
@@ -2773,6 +2800,7 @@
 	inserthorizontalrule: "",
 	inserthtml: "ab<b>c</b>d",
 	insertimage: "/img/lion.svg",
+	inserttext: "a",
 };
 
 var notes = {