Start to preserve state/value in more places
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Thu, 21 Jul 2011 15:03:51 -0600
changeset 437 755a8d43ea86
parent 436 60444f81966a
child 438 ee56099364e6
Start to preserve state/value in more places

This is probably buggy, I'm just committing it because I want to stop
having to use git add -i and don't want to deal with merge conflicts if
I stash.
editcommands.html
implementation.js
source.html
--- a/editcommands.html	Thu Jul 21 15:03:28 2011 -0600
+++ b/editcommands.html	Thu Jul 21 15:03:51 2011 -0600
@@ -38,7 +38,7 @@
 <body class=draft>
 <div class=head id=head>
 <h1>HTML Editing Commands</h1>
-<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-18-july-2011>Work in Progress &mdash; Last Update 18 July 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-20-july-2011>Work in Progress &mdash; Last Update 20 July 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;<a href=mailto:ayg@aryeh.name>ayg@aryeh.name</a>&gt;
@@ -322,6 +322,8 @@
 
   <li>Have to make sure that in all the places where we set a selection, it's
   valid.
+
+  <li>Redefine things in terms of ranges, not selections.
 </ul>
 
 <p class=XXX>A variety of other issues are also noted in the text, formatted
@@ -853,6 +855,12 @@
 <p class=XXX>Figure out exactly what commands need to preserve state/value
 overrides.
 
+<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>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,
@@ -971,6 +979,51 @@
 <a href=#remove-extraneous-line-breaks-before>remove extraneous line breaks before</a> it, then <a href=#remove-extraneous-line-breaks-at-the-end-of>remove
 extraneous line breaks at the end of</a> it.
 
+<p>To <dfn id=record-current-states-and-values>record current states and values</dfn>:
+
+<ol>
+  <li>Let <var title="">states</var> be a dictionary mapping <a href=#command title=command>commands</a> to booleans, initially empty.
+
+  <li>For each <a href=#command-with-insertion-affecting-state>command with insertion-affecting state</a>
+  <var title="">command</var>, in order, add an entry to <var title="">states</var> mapping
+  <var title="">command</var> to the result of <code title=queryCommandState()><a href=#querycommandstate()>queryCommandState(<var title="">command</var>)</a></code>.
+
+  <li>Let <var title="">values</var> be a dictionary mapping <a href=#command title=command>commands</a> to strings, initially empty.
+
+  <li>For each <a href=#command-with-insertion-affecting-value>command with insertion-affecting value</a>
+  <var title="">command</var>, in order, add an entry to <var title="">values</var> mapping
+  <var title="">command</var> to the result of <code title=queryCommandValue()><a href=#querycommandvalue()>queryCommandValue(<var title="">command</var>)</a></code>.
+
+  <li>Return (<var title="">states</var>, <var title="">values</var>).
+</ol>
+
+<p>To <dfn id=restore-states-and-values>restore states and values</dfn> specified by dictionaries
+(<var title="">states</var>, <var title="">values</var>) returned by the <a href=#record-current-states-and-values>record current
+states and values</a> algorithm:
+
+<ol>
+  <li>For each <var title="">command</var> that is a key in <var title="">states</var>, in order:
+
+  <ol>
+    <li>Let <var title="">state</var> be the value of <var title="">command</var> in
+    <var title="">states</var>.
+
+    <li>If <code title=queryCommandState()><a href=#querycommandstate()>queryCommandState(<var title="">command</var>)</a></code>
+    returns something different from <var title="">state</var>, call <code title=execCommand()><a href=#execcommand()>execCommand(<var title="">command</var>)</a></code>.
+  </ol>
+
+  <li>For each <var title="">command</var> that is a key in <var title="">values</var>, in order:
+
+  <ol>
+    <li>Let <var title="">value</var> be the value of <var title="">command</var> in
+    <var title="">values</var>.
+
+    <li>If <code title=queryCommandValue()><a href=#querycommandvalue()>queryCommandValue(<var title="">command</var>)</a></code>
+    returns something different from <var title="">value</var>, call <code title=execCommand()><a href=#execcommand()>execCommand(<var title="">command</var>, false,
+    <var title="">value</var>)</a></code>.
+  </ol>
+</ol>
+
 
 <h3 id=wrapping-a-list-of-nodes><span class=secno>6.2 </span>Wrapping a list of nodes</h3>
 
@@ -3606,8 +3659,6 @@
 <!-- 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.
 
@@ -3777,6 +3828,9 @@
   <var title="">end block</var>, or <var title="">end block</var> is a <code class=external data-anolis-spec=html title="the td element"><a href=http://www.whatwg.org/html/#the-td-element>td</a></code> or <code class=external data-anolis-spec=html title="the th element"><a href=http://www.whatwg.org/html/#the-th-element>th</a></code>, set
   <var title="">end block</var> to null.
 
+  <li><a href=#record-current-states-and-values>Record current states and values</a>, and let <var title="">record</var>
+  be the result.
+
   <!-- This is based on deleteContents() in DOM Range. -->
   <li>If <var title="">start node</var> and <var title="">end node</var> are the same, and
   <var title="">start node</var> is an <a href=#editable>editable</a> <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node:
@@ -3790,6 +3844,11 @@
 
     <li>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>.
 
+    <li><a href=#restore-states-and-values>Restore states and values</a> from <var title="">record</var>.
+    <!-- This is needed to restore any overrides that would otherwise be lost.
+    TODO: In this and similar cases, we could optimize by saving only
+    overrides, not the full state/value. -->
+
     <li>Abort these steps.
   </ol>
 
@@ -3866,8 +3925,15 @@
   <li>If <var title="">block merging</var> is false, or <var title="">start block</var> or
   <var title="">end block</var> is null, or <var title="">start block</var> is not <a href=#in-the-same-editing-host>in the
   same editing host</a> as <var title="">end block</var>, or <var title="">start block</var>
-  and <var title="">end block</var> are the same, set <var title="">range</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> to
-  its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and then abort these steps.
+  and <var title="">end block</var> are the same:
+
+  <ol>
+    <li>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>.
+
+    <li><a href=#restore-states-and-values>Restore states and values</a> from <var title="">record</var>.
+
+    <li>Abort these steps.
+  </ol>
 
   <!--
   We might have added a br to the start/end block in an earlier step.  Now
@@ -3912,11 +3978,14 @@
       <li>If <var title="">end block</var> is <a href=#editable>editable</a>, remove it from 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><a href=#restore-states-and-values>Restore states and values</a> from <var title="">record</var>.
+
       <li>Abort these steps.
     </ol>
 
     <li>If <var title="">end block</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-firstChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-firstchild>firstChild</a></code> is not an <a href=#inline-node>inline
-    node</a>, abort these steps.
+    node</a>, <a href=#restore-states-and-values>restore states and values</a> from <var title="">record</var>,
+    then abort these steps.
 
     <li>Let <var title="">children</var> be a list of <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>, initially empty.
 
@@ -4006,6 +4075,8 @@
   <li>If <var title="">start block</var> has no <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, call
   <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("br")</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a> and append the result as the
   last <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">start block</var>.
+
+  <li><a href=#restore-states-and-values>Restore states and values</a> from <var title="">record</var>.
 </ol>
 
 
@@ -6724,12 +6795,6 @@
 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 <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>:
 
 <!--
@@ -6864,11 +6929,8 @@
   "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><a href=#record-current-states-and-values>Record current states and values</a>, and let <var title="">record</var>
+  be 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:
 
@@ -6903,29 +6965,7 @@
     <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=#restore-states-and-values>Restore states and values</a> from <var title="">record</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/implementation.js	Thu Jul 21 15:03:28 2011 -0600
+++ b/implementation.js	Thu Jul 21 15:03:51 2011 -0600
@@ -1051,6 +1051,17 @@
 		delete valueOverrides[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"];
+
 //@}
 
 /////////////////////////////
@@ -1235,6 +1246,61 @@
 	removeExtraneousLineBreaksAtTheEndOf(node);
 }
 
+function recordCurrentStatesAndValues() {
+	// "Let states be a dictionary mapping commands to booleans, initially
+	// empty."
+	var states = {};
+
+	// "For each command with insertion-affecting state command, in order, add
+	// an entry to states mapping command to the result of
+	// queryCommandState(command)."
+	commandsWithInsertionAffectingState.forEach(function(command) {
+		states[command] = myQueryCommandState(command);
+	});
+
+	// "Let values be a dictionary mapping commands to strings, initially
+	// empty."
+	var values = {};
+
+	// "For each command with insertion-affecting value command, in order, add
+	// an entry to values mapping command to the result of
+	// queryCommandValue(command)."
+	commandsWithInsertionAffectingValue.forEach(function(command) {
+		values[command] = myQueryCommandValue(command);
+	});
+
+	// "Return (states, values)."
+	return [states, values];
+}
+
+function restoreStatesAndValues(record) {
+	var states = record[0];
+	var values = record[1];
+
+	// "For each command that is a key in states, in order:"
+	for (var command in states) {
+		// "Let state be the value of command in states."
+		var state = states[command];
+
+		// "If queryCommandState(command) returns something different from
+		// state, call execCommand(command)."
+		if (myQueryCommandState(command) !== state) {
+			myExecCommand(command);
+		}
+	}
+
+	// "For each command that is a key in values, in order:"
+	for (var command in values) {
+		// "Let value be the value of command in values."
+		var value = values[command];
+
+		// "If queryCommandValue(command) returns something different from
+		// value, call execCommand(command, false, value)."
+		if (myQueryCommandValue(command) !== value) {
+			myExecCommand(command, false, value);
+		}
+	}
+}
 
 //@}
 ///// Wrapping a list of nodes /////
@@ -4201,6 +4267,9 @@
 		endBlock = null;
 	}
 
+	// "Record current states and values, and let record be the result."
+	var record = recordCurrentStatesAndValues();
+
 	// "If start node and end node are the same, and start node is an editable
 	// Text node:"
 	if (startNode == endNode
@@ -4216,6 +4285,9 @@
 		// "Set range's end to its start."
 		range.setEnd(range.startContainer, range.startOffset);
 
+		// "Restore states and values from record."
+		restoreStatesAndValues(record);
+
 		// "Abort these steps."
 		return;
 	}
@@ -4286,14 +4358,19 @@
 
 	// "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
-	// and end block are the same, set range's end to its start and then abort
-	// these steps."
+	// and end block are the same:"
 	if (!blockMerging
 	|| !startBlock
 	|| !endBlock
 	|| !inSameEditingHost(startBlock, endBlock)
 	|| startBlock == endBlock) {
+		// "Set range's end to its start."
 		range.setEnd(range.startContainer, range.startOffset);
+
+		// "Restore states and values from record."
+		restoreStatesAndValues(record);
+
+		// "Abort these steps."
 		return;
 	}
 
@@ -4357,13 +4434,17 @@
 				endBlock.parentNode.removeChild(endBlock);
 			}
 
+			// "Restore states and values from record."
+			restoreStatesAndValues(record);
+
 			// "Abort these steps."
 			return;
 		}
 
-		// "If end block's firstChild is not an inline node, abort these
-		// steps."
+		// "If end block's firstChild is not an inline node, restore states and
+		// values from record, then abort these steps."
 		if (!isInlineNode(endBlock.firstChild)) {
+			restoreStatesAndValues(record);
 			return;
 		}
 
@@ -4498,6 +4579,9 @@
 	if (!startBlock.hasChildNodes()) {
 		startBlock.appendChild(document.createElement("br"));
 	}
+
+	// "Restore states and values from record."
+	restoreStatesAndValues(record);
 }
 
 
@@ -6870,16 +6954,6 @@
 //@}
 ///// 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."
@@ -6951,19 +7025,8 @@
 			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());
-		})
+		// "Record current states and values, and let record be the result."
+		var record = recordCurrentStatesAndValues();
 
 		// "If node is a Text node:"
 		if (node.nodeType == Node.TEXT_NODE) {
@@ -7003,30 +7066,8 @@
 			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());
-			}
-		});
+		// "Restore states and values from record."
+		restoreStatesAndValues(record);
 
 		// "Canonicalize whitespace at the active range's start."
 		canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);
--- a/source.html	Thu Jul 21 15:03:28 2011 -0600
+++ b/source.html	Thu Jul 21 15:03:51 2011 -0600
@@ -254,6 +254,8 @@
 
   <li>Have to make sure that in all the places where we set a selection, it's
   valid.
+
+  <li>Redefine things in terms of ranges, not selections.
 </ul>
 
 <p class=XXX>A variety of other issues are also noted in the text, formatted
@@ -805,6 +807,12 @@
 <p class=XXX>Figure out exactly what commands need to preserve state/value
 overrides.
 
+<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>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,
@@ -925,6 +933,59 @@
 <span>remove extraneous line breaks before</span> it, then <span>remove
 extraneous line breaks at the end of</span> it.
 
+<p>To <dfn>record current states and values</dfn>:
+
+<ol>
+  <li>Let <var>states</var> be a dictionary mapping <span
+  title=command>commands</span> to booleans, initially empty.
+
+  <li>For each <span>command with insertion-affecting state</span>
+  <var>command</var>, in order, add an entry to <var>states</var> mapping
+  <var>command</var> to the result of <code
+  title=queryCommandState()>queryCommandState(<var>command</var>)</code>.
+
+  <li>Let <var>values</var> be a dictionary mapping <span
+  title=command>commands</span> to strings, initially empty.
+
+  <li>For each <span>command with insertion-affecting value</span>
+  <var>command</var>, in order, add an entry to <var>values</var> mapping
+  <var>command</var> to the result of <code
+  title=queryCommandValue()>queryCommandValue(<var>command</var>)</code>.
+
+  <li>Return (<var>states</var>, <var>values</var>).
+</ol>
+
+<p>To <dfn>restore states and values</dfn> specified by dictionaries
+(<var>states</var>, <var>values</var>) returned by the <span>record current
+states and values</span> algorithm:
+
+<ol>
+  <li>For each <var>command</var> that is a key in <var>states</var>, in order:
+
+  <ol>
+    <li>Let <var>state</var> be the value of <var>command</var> in
+    <var>states</var>.
+
+    <li>If <code
+    title=queryCommandState()>queryCommandState(<var>command</var>)</code>
+    returns something different from <var>state</var>, call <code
+    title=execCommand()>execCommand(<var>command</var>)</code>.
+  </ol>
+
+  <li>For each <var>command</var> that is a key in <var>values</var>, in order:
+
+  <ol>
+    <li>Let <var>value</var> be the value of <var>command</var> in
+    <var>values</var>.
+
+    <li>If <code
+    title=queryCommandValue()>queryCommandValue(<var>command</var>)</code>
+    returns something different from <var>value</var>, call <code
+    title=execCommand()>execCommand(<var>command</var>, false,
+    <var>value</var>)</code>.
+  </ol>
+</ol>
+
 <!-- @} -->
 <h3>Wrapping a list of nodes</h3>
 <!-- @{ -->
@@ -3593,8 +3654,6 @@
 <!-- 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.
 
@@ -3764,6 +3823,9 @@
   <var>end block</var>, or <var>end block</var> is a [[td]] or [[th]], set
   <var>end block</var> to null.
 
+  <li><span>Record current states and values</span>, and let <var>record</var>
+  be the result.
+
   <!-- This is based on deleteContents() in DOM Range. -->
   <li>If <var>start node</var> and <var>end node</var> are the same, and
   <var>start node</var> is an <span>editable</span> [[text]] node:
@@ -3777,6 +3839,11 @@
 
     <li>Set <var>range</var>'s [[rangeend]] to its [[rangestart]].
 
+    <li><span>Restore states and values</span> from <var>record</var>.
+    <!-- This is needed to restore any overrides that would otherwise be lost.
+    TODO: In this and similar cases, we could optimize by saving only
+    overrides, not the full state/value. -->
+
     <li>Abort these steps.
   </ol>
 
@@ -3853,8 +3920,15 @@
   <li>If <var>block merging</var> is false, or <var>start block</var> or
   <var>end block</var> is null, or <var>start block</var> is not <span>in the
   same editing host</span> as <var>end block</var>, or <var>start block</var>
-  and <var>end block</var> are the same, set <var>range</var>'s [[rangeend]] to
-  its [[rangestart]] and then abort these steps.
+  and <var>end block</var> are the same:
+
+  <ol>
+    <li>Set <var>range</var>'s [[rangeend]] to its [[rangestart]].
+
+    <li><span>Restore states and values</span> from <var>record</var>.
+
+    <li>Abort these steps.
+  </ol>
 
   <!--
   We might have added a br to the start/end block in an earlier step.  Now
@@ -3899,11 +3973,14 @@
       <li>If <var>end block</var> is <span>editable</span>, remove it from its
       [[parent]].
 
+      <li><span>Restore states and values</span> from <var>record</var>.
+
       <li>Abort these steps.
     </ol>
 
     <li>If <var>end block</var>'s [[firstchild]] is not an <span>inline
-    node</span>, abort these steps.
+    node</span>, <span>restore states and values</span> from <var>record</var>,
+    then abort these steps.
 
     <li>Let <var>children</var> be a list of [[nodes]], initially empty.
 
@@ -3993,6 +4070,8 @@
   <li>If <var>start block</var> has no [[children]], call
   [[createelement|"br"]] on the [[contextobject]] and append the result as the
   last [[child]] of <var>start block</var>.
+
+  <li><span>Restore states and values</span> from <var>record</var>.
 </ol>
 
 <!-- @} -->
@@ -6726,12 +6805,6 @@
 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 <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>:
 
 <!--
@@ -6866,11 +6939,8 @@
   "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><span>Record current states and values</span>, and let <var>record</var>
+  be the result.
 
   <li>If <var>node</var> is a [[text]] node:
 
@@ -6907,32 +6977,7 @@
     [[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>Restore states and values</span> from <var>record</var>.
 
   <li><span>Canonicalize whitespace</span> at the <span>active range</span>'s
   [[rangestart]].