Support state/value overrides for insertText
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Mon, 18 Jul 2011 15:48:09 -0600
changeset 433 eccd28918621
parent 432 bf4a52cafc8b
child 434 91d75263f14b
Support state/value overrides for insertText
editcommands.html
implementation.js
preprocess
source.html
--- a/editcommands.html	Mon Jul 18 15:26:31 2011 -0600
+++ b/editcommands.html	Mon Jul 18 15:48:09 2011 -0600
@@ -850,6 +850,9 @@
 different, the <a href=#state-override>state override</a> and <a href=#value-override>value override</a> must
 be unset for every <a href=#command>command</a>.
 
+<p class=XXX>Figure out exactly what commands need to preserve state/value
+overrides.
+
 <p>When the user agent is instructed to run a particular method, it must follow
 the steps defined for that method in the appropriate specification, not act as
 though the method had actually been called from JavaScript.  In particular,
@@ -3603,6 +3606,8 @@
 <!-- TODO: Consider what should happen for block merging in corner cases like
 display: inline-table. -->
 
+<p class=XXX>Needs to preserve state/value in some cases?
+
 <ol>
   <li>If <var title="">range</var> is null, abort these steps and do nothing.
 
@@ -3699,9 +3704,9 @@
     offset</var> to 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="">reference node</var>.
   </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>), 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="">end node</var>, <var title="">end offset</var>) is not <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-bp-after title=concept-bp-after>after</a>
+  (<var title="">start 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> 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>,
@@ -6719,8 +6724,11 @@
 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 class=XXX>Needs to handle overridden state/value.
+<p>A <dfn id=command-with-insertion-affecting-state>command with insertion-affecting state</dfn> is "bold", "italic",
+"strikethrough", "subscript", "superscript", or "underline".
+
+<p>A <dfn id=command-with-insertion-affecting-value>command with insertion-affecting value</dfn> is "createLink",
+"fontName", "fontSize", "foreColor", or "hiliteColor".
 
 <p><a href=#action>Action</a>:
 
@@ -6856,38 +6864,76 @@
   "pre-wrap", set <var title="">value</var> to a non-breaking space (U+00A0).
   <!-- This may change to a space when we canonicalize. -->
 
+  <li>For each <a href=#command-with-insertion-affecting-state>command with insertion-affecting state</a>, in order,
+  call <code><a href=#querycommandstate()>queryCommandState()</a></code> and record the result.
+
+  <li>For each <a href=#command-with-insertion-affecting-value>command with insertion-affecting value</a>, in order,
+  call <code><a href=#querycommandvalue()>queryCommandValue()</a></code> and record the result.
+
   <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 <var title="">value</var>'s <a href=http://es5.github.com/#x15.5.5.1>length</a> 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><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at (<var title="">node</var>,
-    <var title="">offset</var> &minus; 1).
-
-    <li><a href=#canonicalize-whitespace>Canonicalize whitespace</a> at (<var title="">node</var>,
-    <var title="">offset</var>).
-
-    <li>Abort these steps.
+    <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-extend><a href=http://html5.org/specs/dom-range.html#dom-selection-extend>extend(<var title="">node</var>, <var title="">offset</var> + 1)</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>
 
-  <!-- If some text is inserted into <p><br></p> or similar, we no longer need
-  the <br>. -->
-  <li>If <var title="">node</var> has only 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 <a href=#collapsed-line-break>collapsed
-  line break</a>, 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>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>.
-
-  <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>, 1)</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>Otherwise:
+
+  <ol>
+    <!-- If some text is inserted into <p><br></p> or similar, we no longer need
+    the <br>. -->
+    <li>If <var title="">node</var> has only 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 <a href=#collapsed-line-break>collapsed
+    line break</a>, 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>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>.
+
+    <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>, 0)</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>Call <code class=external data-anolis-spec=domrange title=dom-Selection-extend><a href=http://html5.org/specs/dom-range.html#dom-selection-extend>extend(<var title="">text</var>, 1)</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>
+
+  <li>For each <a href=#command-with-insertion-affecting-state>command with insertion-affecting state</a>
+  <var title="">command</var>, in order:
+
+  <ol>
+    <li>Call <code title=queryCommandState()><a href=#querycommandstate()>queryCommandState(<var title="">command</var>)</a></code>.
+
+    <li>If the result of the last step does not match the recorded
+    <a href=#state>state</a>, call <code title=execCommand()><a href=#execcommand()>execCommand(<var title="">command</var>)</a></code>.
+  </ol>
+
+  <li>For each <a href=#command-with-insertion-affecting-value>command with insertion-affecting value</a>
+  <var title="">command</var>, in order:
+
+  <ol>
+    <li>Let <var title="">recorded value</var> be the recorded <a href=#value>value</a> for
+    <var title="">command</var>.
+
+    <li>Call <code title=queryCommandValue()><a href=#querycommandvalue()>queryCommandValue(<var title="">command</var>)</a></code>.
+
+    <li>If the result of the last step is not <var title="">recorded value</var>, call
+    <code title=execCommand()><a href=#execcommand()>execCommand(<var title="">command</var>, false,
+    <var title="">recorded value</var>)</a></code>.
+  </ol>
+
+  <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>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	Mon Jul 18 15:26:31 2011 -0600
+++ b/implementation.js	Mon Jul 18 15:48:09 2011 -0600
@@ -4123,13 +4123,9 @@
 		endOffset = getNodeLength(referenceNode);
 	}
 
-	// "If (end node, end offset) is before (start node, start offset), set
+	// "If (end node, end offset) is not after (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) {
+	if (getPosition(endNode, endOffset, startNode, startOffset) !== "after") {
 		range.setEnd(range.startContainer, range.startOffset);
 		return;
 	}
@@ -6862,6 +6858,16 @@
 //@}
 ///// The insertText command /////
 //@{
+// "A command with insertion-affecting state is "bold", "italic",
+// "strikethrough", "subscript", "superscript", or "underline"."
+var commandsWithInsertionAffectingState = ["bold", "italic", "strikethrough",
+	"subscript", "superscript", "underline"];
+
+// "A command with insertion-affecting value is "createLink", "fontName",
+// "fontSize", "forecolor", or "hiliteColor"."
+var commandsWithInsertionAffectingValue = ["createlink", "fontname",
+	"fontsize", "forecolor", "hilitecolor"];
+
 commands.inserttext = {
 	action: function(value) {
 		// "Delete the contents of the active range."
@@ -6933,48 +6939,91 @@
 			value = "\xa0";
 		}
 
+		// "For each command with insertion-affecting state, in order, call
+		// queryCommandState() and record the result."
+		var recordedStates = {};
+		commandsWithInsertionAffectingState.forEach(function(command) {
+			recordedStates[command] = myQueryCommandState(command, getActiveRange());
+		});
+
+		// "For each command with insertion-affecting value, in order, call
+		// queryCommandValue() and record the result."
+		var recordedValues = {};
+		commandsWithInsertionAffectingValue.forEach(function(command) {
+			recordedValues[command] = myQueryCommandValue(command, getActiveRange());
+		})
+
 		// "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."
+			//
+			// "Call extend(node, offset + 1) on the context object's
+			// Selection."
 			getActiveRange().setStart(node, offset);
-			getActiveRange().setEnd(node, offset);
-
-			// "Canonicalize whitespace at (node, offset − 1)."
-			canonicalizeWhitespace(node, offset - 1);
-
-			// "Canonicalize whitespace at (node, offset)."
-			canonicalizeWhitespace(node, offset);
-
-			// "Abort these steps."
-			return;
-		}
-
-		// "If node has only one child, which is a collapsed line break, remove
-		// its child from it."
-		//
-		// FIXME: IE incorrectly returns false here instead of true sometimes?
-		if (node.childNodes.length == 1
-		&& isCollapsedLineBreak(node.firstChild)) {
-			node.removeChild(node.firstChild);
-		}
-
-		// "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);
+			getActiveRange().setEnd(node, offset + 1);
+
+		// "Otherwise:"
+		} else {
+			// "If node has only one child, which is a collapsed line break,
+			// remove its child from it."
+			//
+			// FIXME: IE incorrectly returns false here instead of true
+			// sometimes?
+			if (node.childNodes.length == 1
+			&& isCollapsedLineBreak(node.firstChild)) {
+				node.removeChild(node.firstChild);
+			}
+
+			// "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, 0) on the context object's Selection."
+			//
+			// "Call extend(text, 1) on the context object's Selection."
+			getActiveRange().setStart(text, 0);
+			getActiveRange().setEnd(text, 1);
+		}
+
+		// "For each command with insertion-affecting state command, in order:"
+		commandsWithInsertionAffectingState.forEach(function(command) {
+			// "Call queryCommandState(command)."
+			//
+			// "If the result of the last step does not match the recorded
+			// state, call execCommand(command)."
+			if (myQueryCommandState(command, getActiveRange()) !== recordedStates[command]) {
+				myExecCommand(command, false, "", getActiveRange());
+			}
+		});
+
+		// "For each command with insertion-affecting value command, in order:"
+		commandsWithInsertionAffectingValue.forEach(function(command) {
+			// "Let recorded value be the recorded value for command."
+			var recordedValue = recordedValues[command];
+
+			// "Call queryCommandValue(command)."
+			//
+			// "If the result of the last step is not recorded value, call
+			// execCommand(command, false, recorded value)."
+			if (myQueryCommandValue(command, getActiveRange()) !== recordedValue) {
+				myExecCommand(command, false, recordedValue, getActiveRange());
+			}
+		});
+
+		// "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);
+
+		// "Call collapseToEnd() on the context object's Selection."
+		getActiveRange().collapse(false);
 	}
 };
 
--- a/preprocess	Mon Jul 18 15:26:31 2011 -0600
+++ b/preprocess	Mon Jul 18 15:48:09 2011 -0600
@@ -26,6 +26,7 @@
     'cdlength': '<code data-anolis-spec=domcore title=dom-CharacterData-length>length</code>',
     'child': '<span data-anolis-spec=domcore title=concept-tree-child>child</span>',
     'children': '<span data-anolis-spec=domcore title=concept-tree-child>children</span>',
+    'collapsetoend': '<code data-anolis-spec=domrange title=dom-Selection-collapseToEnd>collapseToEnd()</code>',
     'collection': '<span data-anolis-spec=domcore title=concept-collection>collection</span>',
     'contained': '<span data-anolis-spec=domrange>contained</span>',
     'comment': '<code data-anolis-spec=domcore>Comment</code>',
--- a/source.html	Mon Jul 18 15:26:31 2011 -0600
+++ b/source.html	Mon Jul 18 15:48:09 2011 -0600
@@ -802,6 +802,9 @@
 different, the <span>state override</span> and <span>value override</span> must
 be unset for every <span>command</span>.
 
+<p class=XXX>Figure out exactly what commands need to preserve state/value
+overrides.
+
 <p>When the user agent is instructed to run a particular method, it must follow
 the steps defined for that method in the appropriate specification, not act as
 though the method had actually been called from JavaScript.  In particular,
@@ -3590,6 +3593,8 @@
 <!-- TODO: Consider what should happen for block merging in corner cases like
 display: inline-table. -->
 
+<p class=XXX>Needs to preserve state/value in some cases?
+
 <ol>
   <li>If <var>range</var> is null, abort these steps and do nothing.
 
@@ -3686,10 +3691,9 @@
     offset</var> to the [[nodelength]] of <var>reference node</var>.
   </ol>
 
-  <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>), set <var>range</var>'s [[rangeend]] to
-  its [[rangestart]] and abort these steps.
+  <li>If (<var>end node</var>, <var>end offset</var>) is not [[bpafter]]
+  (<var>start 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]] node and <var>start offset</var>
   is 0, set <var>start offset</var> to the [[index]] of <var>start node</var>,
@@ -6722,8 +6726,11 @@
 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 class=XXX>Needs to handle overridden state/value.
+<p>A <dfn>command with insertion-affecting state</dfn> is "bold", "italic",
+"strikethrough", "subscript", "superscript", or "underline".
+
+<p>A <dfn>command with insertion-affecting value</dfn> is "createLink",
+"fontName", "fontSize", "foreColor", or "hiliteColor".
 
 <p><span>Action</span>:
 
@@ -6859,40 +6866,81 @@
   "pre-wrap", set <var>value</var> to a non-breaking space (U+00A0).
   <!-- This may change to a space when we canonicalize. -->
 
+  <li>For each <span>command with insertion-affecting state</span>, in order,
+  call <code>queryCommandState()</code> and record the result.
+
+  <li>For each <span>command with insertion-affecting value</span>, in order,
+  call <code>queryCommandValue()</code> and record the result.
+
   <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 <var>value</var>'s [[strlen]] to <var>offset</var>.
-
     <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> &minus; 1).
-
-    <li><span>Canonicalize whitespace</span> at (<var>node</var>,
-    <var>offset</var>).
-
-    <li>Abort these steps.
+    <li>Call [[extend|<var>node</var>, <var>offset</var> + 1]] on the
+    [[contextobject]]'s [[selection]].
   </ol>
 
-  <!-- If some text is inserted into <p><br></p> or similar, we no longer need
-  the <br>. -->
-  <li>If <var>node</var> has only one [[child]], which is a <span>collapsed
-  line break</span>, remove its [[child]] from it.
-
-  <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]].
-
-  <li>Call [[insertnode|<var>text</var>]] on the <span>active range</span>.
-
-  <li>Call [[selcollapse|<var>text</var>, 1]] on the [[contextobject]]'s
-  [[selection]].
+  <li>Otherwise:
+
+  <ol>
+    <!-- If some text is inserted into <p><br></p> or similar, we no longer need
+    the <br>. -->
+    <li>If <var>node</var> has only one [[child]], which is a <span>collapsed
+    line break</span>, remove its [[child]] from it.
+
+    <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]].
+
+    <li>Call [[insertnode|<var>text</var>]] on the <span>active range</span>.
+
+    <li>Call [[selcollapse|<var>text</var>, 0]] on the [[contextobject]]'s
+    [[selection]].
+
+    <li>Call [[extend|<var>text</var>, 1]] on the [[contextobject]]'s
+    [[selection]].
+  </ol>
+
+  <li>For each <span>command with insertion-affecting state</span>
+  <var>command</var>, in order:
+
+  <ol>
+    <li>Call <code
+    title=queryCommandState()>queryCommandState(<var>command</var>)</code>.
+
+    <li>If the result of the last step does not match the recorded
+    <span>state</span>, call <code
+    title=execCommand()>execCommand(<var>command</var>)</code>.
+  </ol>
+
+  <li>For each <span>command with insertion-affecting value</span>
+  <var>command</var>, in order:
+
+  <ol>
+    <li>Let <var>recorded value</var> be the recorded <span>value</span> for
+    <var>command</var>.
+
+    <li>Call <code
+    title=queryCommandValue()>queryCommandValue(<var>command</var>)</code>.
+
+    <li>If the result of the last step is not <var>recorded value</var>, call
+    <code title=execCommand()>execCommand(<var>command</var>, false,
+    <var>recorded value</var>)</code>.
+  </ol>
+
+  <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>Call [[collapsetoend]] on the [[contextobject]]'s [[selection]].
 </ol>
 
 <!-- @} -->