Refine insertText a bit
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Sun, 19 Jun 2011 12:33:21 -0600
changeset 292 0042250f7e87
parent 291 47b1dc8e4e8e
child 293 29ed14779368
Refine insertText a bit
autoimplementation.html
editcommands.html
implementation.js
preprocess
source.html
tests.js
--- a/autoimplementation.html	Sun Jun 19 10:59:43 2011 -0600
+++ b/autoimplementation.html	Sun Jun 19 12:33:21 2011 -0600
@@ -63,14 +63,22 @@
 	for (var i = 0; i < tests[command].length; i++) {
 		// This code actually focuses and clicks everything because for some
 		// reason, anything else doesn't work in IE9 . . .
+		//
+		// In case the input contains something unpleasant like newlines, we
+		// smuggle in the string as a data attribute, not just the value.  This
+		// is probably unnecessarily magic.
 		if (typeof tests[command][i] == "string") {
 			inputs[0].value = tests[command][i];
+			inputs[0].setAttribute("data-input", tests[command][i]);
 			if (inputs.length == 2) {
 				inputs[1].value = defaultValues[command];
+				inputs[1].setAttribute("data-input", defaultValues[command]);
 			}
 		} else {
 			inputs[0].value = tests[command][i][1];
+			inputs[0].setAttribute("data-input", tests[command][i][1]);
 			inputs[1].value = tests[command][i][0];
+			inputs[1].setAttribute("data-input", tests[command][i][0]);
 		}
 		inputs[0].focus();
 		addTestButton.click();
@@ -97,11 +105,22 @@
 
 	var test;
 	var inputs = document.getElementById(command).getElementsByTagName("input");
+
 	if (inputs.length == 1) {
-		test = inputs[0].value;
+		if (inputs[0].hasAttribute("data-input")) {
+			test = inputs[0].getAttribute("data-input");
+		} else {
+			test = inputs[0].value;
+		}
 	} else {
-		test = [inputs[1].value, inputs[0].value];
+		if (inputs[0].hasAttribute("data-input")) {
+			test = [inputs[1].getAttribute("data-input"), inputs[0].getAttribute("data-input")];
+			inputs[1].removeAttribute("data-input");
+		} else {
+			test = [inputs[1].value, inputs[0].value];
+		}
 	}
+	inputs[0].removeAttribute("data-input");
 
 	doInputCell(tr, test);
 	doSpecCell(tr, test, command, false);
--- a/editcommands.html	Sun Jun 19 10:59:43 2011 -0600
+++ b/editcommands.html	Sun Jun 19 12:33:21 2011 -0600
@@ -5043,10 +5043,17 @@
 
 <ol>
   <li><a href=#delete-the-contents>Delete the contents</a> of the <a href=#active-range>active range</a>.
+  <!-- Firefox 5.0a2 seems to not do this if value is empty; Chrome 14 dev and
+  Opera 11.11 do. -->
 
   <li>Let <var title="">frag</var> be the result of calling <code class=external data-anolis-spec=domps title=dom-Range-createContextualFragment><a href=http://html5.org/specs/dom-parsing.html#dom-range-createcontextualfragment>createContextualFragment(<var title="">value</var>)</a></code>
   on the <a href=#active-range>active range</a>.
 
+  <p class=XXX>This has some interesting consequences.  For instance, table
+  cells and similar will just vanish if they're not in an appropriate place;
+  and inside a script or style or xmp or such, the argument will effectively be
+  HTML-escaped before use.  Some of these consequences might be undesirable.
+
   <li>Let <var title="">last child</var> be the <code class=external data-anolis-spec=domcore title=dom-Node-lastChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-lastchild>lastChild</a></code> of <var title="">frag</var>.
 
   <li>If <var title="">last child</var> is null, abort these steps.
@@ -5452,15 +5459,53 @@
 
   <li>If <var title="">value</var> is the empty string, abort these steps.
 
+  <p class=XXX>WebKit does magic for tabs, and actually handles newlines as
+  newlines.
+
+  <p class=XXX>This doesn't work well if the input contains things that aren't
+  supposed to appear in HTML, like carriage returns or nulls.  Nor is it going
+  to work well if the current cursor position is in between two halves of a
+  non-BMP character.  This will result in unserializability.  The current spec
+  disregards this, as Chrome 14 dev does.
+
+  <li>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>.
+
+  <!--
+  Just to be tidy, add to an existing text node if there is one.  Firefox 5.0a2
+  only adds to an existing one if the range is in a text node.  IE9, Chrome 14
+  dev, and Opera 11.11 also add to an existing text node if the range is in an
+  element adjacent to a text node.  If there are two text nodes and it's in
+  between, like foo{}bar, IE and Opera add to the first, Chrome adds to the
+  second, although it probably doesn't matter in practice exactly which we
+  choose.
+  -->
+  <li>If <var title="">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> whose <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> is <var title="">offset</var>
+  &minus; 1, 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 <var title="">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>If <var title="">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> whose <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> is <var title="">offset</var>,
+  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>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>
+    <li>Call <code class=external data-anolis-spec=domcore title=dom-CharacterData-insertData><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-insertdata>insertData(<var title="">offset</var>, <var title="">value</var>)</a></code> on
+    <var title="">node</var>.
+
+    <li>Add the <a href=http://es5.github.com/#x15.5.5.1>length</a> of
+    <var title="">value</var> to <var title="">offset</var>.
+
+    <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>Abort these steps.
+  </ol>
+
   <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/implementation.js	Sun Jun 19 10:59:43 2011 -0600
+++ b/implementation.js	Sun Jun 19 12:33:21 2011 -0600
@@ -5600,6 +5600,45 @@
 			return;
 		}
 
+		// "Let node and offset be the active range's start node and offset."
+		var node = getActiveRange().startContainer;
+		var offset = getActiveRange().startOffset;
+
+		// "If node has a child whose index is offset − 1, and that child is a
+		// Text node, set node to that child, then set offset to node's
+		// length."
+		if (0 <= offset - 1
+		&& offset - 1 < node.childNodes.length
+		&& node.childNodes[offset - 1].nodeType == Node.TEXT_NODE) {
+			node = node.childNodes[offset - 1];
+			offset = getNodeLength(node);
+		}
+
+		// "If node has a child whose index is offset, and that child is a Text
+		// node, set node to that child, then set offset to zero."
+		if (0 <= offset
+		&& offset < node.childNodes.length
+		&& node.childNodes[offset].nodeType == Node.TEXT_NODE) {
+			node = node.childNodes[offset];
+			offset = 0;
+		}
+
+		// "If node is a Text node:"
+		if (node.nodeType == Node.TEXT_NODE) {
+			// "Call insertData(offset, value) on node."
+			node.insertData(offset, value);
+
+			// "Add the length of value to offset."
+			offset += value.length;
+
+			// "Call collapse(node, offset) on the context object's Selection."
+			getActiveRange().setStart(node, offset);
+			getActiveRange().setEnd(node, offset);
+
+			// "Abort these steps."
+			return;
+		}
+
 		// "Let text be the result of calling createTextNode(value) on the
 		// context object."
 		var text = document.createTextNode(value);
--- a/preprocess	Sun Jun 19 10:59:43 2011 -0600
+++ b/preprocess	Sun Jun 19 12:33:21 2011 -0600
@@ -116,6 +116,7 @@
     '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>',
     'extend': '<code data-anolis-spec=domrange title=dom-Selection-extend>extend(\\1)</code>',
+    'insertdata': '<code data-anolis-spec=domcore title=dom-CharacterData-insertData>insertData(\\1)</code>',
     'insertnode': '<code data-anolis-spec=domrange title=dom-Range-insertNode>insertNode(\\1)</code>',
     'selcollapse': '<code data-anolis-spec=domrange title=dom-Selection-collapse>collapse(\\1)</code>',
     'setattribute': '<code data-anolis-spec=domcore title=dom-Element-setAttribute>setAttribute(\\1)</code>',
--- a/source.html	Sun Jun 19 10:59:43 2011 -0600
+++ b/source.html	Sun Jun 19 12:33:21 2011 -0600
@@ -5048,11 +5048,18 @@
 
 <ol>
   <li><span>Delete the contents</span> of the <span>active range</span>.
+  <!-- Firefox 5.0a2 seems to not do this if value is empty; Chrome 14 dev and
+  Opera 11.11 do. -->
 
   <li>Let <var>frag</var> be the result of calling <code data-anolis-spec=domps
   title=dom-Range-createContextualFragment>createContextualFragment(<var>value</var>)</code>
   on the <span>active range</span>.
 
+  <p class=XXX>This has some interesting consequences.  For instance, table
+  cells and similar will just vanish if they're not in an appropriate place;
+  and inside a script or style or xmp or such, the argument will effectively be
+  HTML-escaped before use.  Some of these consequences might be undesirable.
+
   <li>Let <var>last child</var> be the [[lastchild]] of <var>frag</var>.
 
   <li>If <var>last child</var> is null, abort these steps.
@@ -5471,17 +5478,55 @@
 
   <li>If <var>value</var> is the empty string, abort these steps.
 
+  <p class=XXX>WebKit does magic for tabs, and actually handles newlines as
+  newlines.
+
+  <p class=XXX>This doesn't work well if the input contains things that aren't
+  supposed to appear in HTML, like carriage returns or nulls.  Nor is it going
+  to work well if the current cursor position is in between two halves of a
+  non-BMP character.  This will result in unserializability.  The current spec
+  disregards this, as Chrome 14 dev does.
+
+  <li>Let <var>node</var> and <var>offset</var> be the <span>active
+  range</span>'s [[startnode]] and [[bpoffset]].
+
+  <!--
+  Just to be tidy, add to an existing text node if there is one.  Firefox 5.0a2
+  only adds to an existing one if the range is in a text node.  IE9, Chrome 14
+  dev, and Opera 11.11 also add to an existing text node if the range is in an
+  element adjacent to a text node.  If there are two text nodes and it's in
+  between, like foo{}bar, IE and Opera add to the first, Chrome adds to the
+  second, although it probably doesn't matter in practice exactly which we
+  choose.
+  -->
+  <li>If <var>node</var> has a [[child]] whose [[index]] is <var>offset</var>
+  &minus; 1, and that [[child]] is a [[text]] node, set <var>node</var> to that
+  [[child]], then set <var>offset</var> to <var>node</var>'s [[length]].
+
+  <li>If <var>node</var> has a [[child]] whose [[index]] is <var>offset</var>,
+  and that [[child]] is a [[text]] node, set <var>node</var> to that [[child]],
+  then set <var>offset</var> to zero.
+
+  <li>If <var>node</var> is a [[text]] node:
+
+  <ol>
+    <li>Call [[insertdata|<var>offset</var>, <var>value</var>]] on
+    <var>node</var>.
+
+    <li>Add the <a href=http://es5.github.com/#x15.5.5.1>length</a> of
+    <var>value</var> to <var>offset</var>.
+
+    <li>Call [[selcollapse|<var>node</var>, <var>offset</var>]] on the
+    [[contextobject]]'s [[selection]].
+
+    <li>Abort these steps.
+  </ol>
+
   <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
--- a/tests.js	Sun Jun 19 10:59:43 2011 -0600
+++ b/tests.js	Sun Jun 19 12:33:21 2011 -0600
@@ -1378,6 +1378,11 @@
 	//@{
 		'foo[]bar',
 		'foo[bar]baz',
+		['', 'foo[bar]baz'],
+		['\0', 'foo[bar]baz'],
+		['\x07', 'foo[bar]baz'],
+		['\ud800', 'foo[bar]baz'],
+
 		['<b>', 'foo[bar]baz'],
 		['<b>abc', 'foo[bar]baz'],
 		['<p>abc', '<p>foo[bar]baz'],
@@ -1388,6 +1393,9 @@
 
 		['abc', '<xmp>f[o]o</xmp>'],
 		['<b>abc</b>', '<xmp>f[o]o</xmp>'],
+		['abc', '<script>f[o]o</script>bar'],
+		['<b>abc</b>', '<script>f[o]o</script>bar'],
+
 		['<a>abc</a>', '<a>f[o]o</a>'],
 		['<a href=/>abc</a>', '<a href=.>f[o]o</a>'],
 		['<hr>', '<p>f[o]o'],
@@ -1777,11 +1785,18 @@
 	//@{
 		'foo[bar]baz',
 		['', 'foo[bar]baz'],
+
 		['\t', 'foo[]bar'],
 		[' ', 'foo[]bar'],
+		['&', 'foo[]bar'],
 		['\n', 'foo[]bar'],
 		['\r', 'foo[]bar'],
 		['\r\n', 'foo[]bar'],
+		['abc\ndef', 'foo[]bar'],
+		['\0', 'foo[]bar'],
+		['\ud800', 'foo[]bar'],
+		['\x07', 'foo[]bar'],
+
 		'foo[]bar',
 		'<p>foo[]',
 		'<p>foo</p>{}',
@@ -2790,6 +2805,7 @@
 };
 
 var defaultValues = {
+//@{
 	backcolor: "#FF8888",
 	createlink: "http://www.google.com/",
 	fontname: "sans-serif",
@@ -2802,6 +2818,7 @@
 	insertimage: "/img/lion.svg",
 	inserttext: "a",
 };
+//@}
 
 var notes = {
 	backcolor: '<strong>Note:</strong> No spec has yet been written, so the spec column does nothing.',
@@ -2810,6 +2827,7 @@
 };
 
 var doubleTestingCommands = [
+//@{
 	"backcolor",
 	"bold",
 	"fontname",
@@ -2825,6 +2843,52 @@
 	"superscript",
 	"underline",
 ];
+//@}
+
+function prettyPrint(value) {
+//@{
+	// Stolen from testharness.js
+	for (var i = 0; i < 32; i++) {
+		var replace = "\\";
+		switch (i) {
+		case 0: replace += "0"; break;
+		case 1: replace += "x01"; break;
+		case 2: replace += "x02"; break;
+		case 3: replace += "x03"; break;
+		case 4: replace += "x04"; break;
+		case 5: replace += "x05"; break;
+		case 6: replace += "x06"; break;
+		case 7: replace += "x07"; break;
+		case 8: replace += "b"; break;
+		case 9: replace += "t"; break;
+		case 10: replace += "n"; break;
+		case 11: replace += "v"; break;
+		case 12: replace += "f"; break;
+		case 13: replace += "r"; break;
+		case 14: replace += "x0e"; break;
+		case 15: replace += "x0f"; break;
+		case 16: replace += "x10"; break;
+		case 17: replace += "x11"; break;
+		case 18: replace += "x12"; break;
+		case 19: replace += "x13"; break;
+		case 20: replace += "x14"; break;
+		case 21: replace += "x15"; break;
+		case 22: replace += "x16"; break;
+		case 23: replace += "x17"; break;
+		case 24: replace += "x18"; break;
+		case 25: replace += "x19"; break;
+		case 26: replace += "x1a"; break;
+		case 27: replace += "x1b"; break;
+		case 28: replace += "x1c"; break;
+		case 29: replace += "x1d"; break;
+		case 30: replace += "x1e"; break;
+		case 31: replace += "x1f"; break;
+		}
+		value = value.replace(String.fromCharCode(i), replace);
+	}
+	return value;
+}
+//@}
 
 function doSetup(selector, idx) {
 //@{
@@ -2849,6 +2913,7 @@
 	inputCell.firstChild.innerHTML = test;
 	inputCell.lastChild.textContent = inputCell.firstChild.innerHTML;
 	if (value !== null) {
+		value = prettyPrint(value);
 		inputCell.lastChild.textContent += ' (value: "' + value + '")';
 	}
 	tr.appendChild(inputCell);