Some further adjustments
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Fri, 18 Mar 2011 16:14:18 -0600
changeset 17 7d1fa07e4e60
parent 16 b3393741ce02
child 18 676aa904bd45
Some further adjustments
autoimplementation.html
editcommands.html
implementation.js
source.html
--- a/autoimplementation.html	Thu Mar 17 16:14:25 2011 -0600
+++ b/autoimplementation.html	Fri Mar 18 16:14:18 2011 -0600
@@ -1,11 +1,12 @@
 <!doctype html>
-<title>Auto-running execCommand() bold tests</title>
+<title>Auto-running execCommand() tests</title>
 <style>
 .yes { color: green }
 .no { color: red }
 b, strong { font-weight: bold }
 .bold { font-weight: bold }
 .notbold { font-weight: normal }
+#purple { color: purple }
 td > div:first-child {
 	padding-bottom: 0.2em;
 	border-bottom: 1px solid black;
@@ -13,105 +14,266 @@
 td > div:last-child {
 	padding-top: 0.2em;
 }
+/* https://bugs.webkit.org/show_bug.cgi?id=56670 */
+dfn { font-style: italic }
+/* We don't want test cells to not wrap */
+listing, plaintext, pre, xmp { white-space: pre-wrap }
 </style>
 <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>.
 
+<h1>Table of Contents</h1>
+<ul>
+	<li><a href=#bold>bold</a>
+	<li><a href=#fontname>fontname</a>
+	<li><a href=#forecolor>forecolor</a>
+	<li><a href=#italic>italic</a>
+</ul>
+
+<div id=bold>
+<h1>bold</h1>
+
+<table border=1><tr><th>Input <th>Spec <th>Browser <th>Same?</table>
+
 <p><label>Enter new test here: <input></label>
-<button onclick="addTest(document.querySelector('input').value)">Add test</button>
+<button onclick="addTest('bold', document.querySelector('input').value)">Add test</button>
+</div>
 
-<div>
+<div id=fontname>
+<h1>fontname</h1>
+
+<p>Tests set the font-family to "serif".
+
 <table border=1><tr><th>Input <th>Spec <th>Browser <th>Same?</table>
+
+<p><label>Enter new test here: <input></label>
+<button onclick="addTest('bold', document.querySelector('input').value)">Add test</button>
+</div>
+
+<div id=forecolor>
+<h1>forecolor</h1>
+
+<p>Tests set the color to "#FF0000".
+
+<table border=1><tr><th>Input <th>Spec <th>Browser <th>Same?</table>
+
+<p><label>Enter new test here: <input></label>
+<button onclick="addTest('forecolor', document.querySelector('input').value)">Add test</button>
+</div>
+
+<div id=italic>
+<h1>italic</h1>
+
+<table border=1><tr><th>Input <th>Spec <th>Browser <th>Same?</table>
+
+<p><label>Enter new test here: <input></label>
+<button onclick="addTest('italic', document.querySelector('input').value)">Add test</button>
 </div>
 
 <script src=implementation.js></script>
 <script>
-// Note: this data format can only yield selections whose start and end are
-// inside text nodes.
-var tests = [
-	'foo[bar]baz',
-	'foo]bar[baz',
-	'foo[bar<i>baz]qoz</i>quz',
-	'foo<span style="font-weight: bold">[bar]</span>baz',
-	'foo<b>[bar]</b>baz',
-	'foo[<b>bar</b>]baz',
-	'foo[<b>bar]</b>baz',
-	'foo<b>[bar</b>]baz',
-	'foo<strong>[bar]</strong>baz',
-	'foo[<strong>bar</strong>]baz',
-	'foo[<strong>bar]</strong>baz',
-	'foo<strong>[bar</strong>]baz',
-	'foo<span style="font-weight: bold">[bar]</span>baz',
-	'foo[<span style="font-weight: bold">bar</span>]baz',
-	'foo[<span style="font-weight: bold">bar]</span>baz',
-	'foo<span style="font-weight: bold">[bar</span>]baz',
-	'<b>{<p>foo</p><p>bar</p>}<p>baz</p></b>',
-	'<b><p>foo[<i>bar</i>}</p><p>baz</p></b>',
-	'foo [bar <b>baz] qoz</b> quz sic',
-	'foo bar <b>baz [qoz</b> quz] sic',
-	'<b id=foo>bar [baz] qoz</b>',
-	'foo<span style="font-weight: 100">[bar]</span>baz',
-	'foo<span style="font-weight: 400">[bar]</span>baz',
-	'foo<span style="font-weight: 700">[bar]</span>baz',
-	'foo<span style="font-weight: 900">[bar]</span>baz',
-	'foo<span style="font-weight: normal">[bar]</span>baz',
-	'foo<span style="font-weight: 100">[bar</span>]baz',
-	'foo<span style="font-weight: 400">[bar</span>]baz',
-	'foo<span style="font-weight: 700">[bar</span>]baz',
-	'foo<span style="font-weight: 900">[bar</span>]baz',
-	'foo<span style="font-weight: normal">[bar</span>]baz',
-	'foo[<span style="font-weight: 100">bar]</span>baz',
-	'foo[<span style="font-weight: 400">bar]</span>baz',
-	'foo[<span style="font-weight: 700">bar]</span>baz',
-	'foo[<span style="font-weight: 900">bar]</span>baz',
-	'foo[<span style="font-weight: normal">bar]</span>baz',
-	'foo[<span style="font-weight: 100">bar</span>]baz',
-	'foo[<span style="font-weight: 400">bar</span>]baz',
-	'foo[<span style="font-weight: 700">bar</span>]baz',
-	'foo[<span style="font-weight: 900">bar</span>]baz',
-	'foo[<span style="font-weight: normal">bar</span>]baz',
-	'<span style="font-weight: 100">foo[bar]baz</span>',
-	'<span style="font-weight: 400">foo[bar]baz</span>',
-	'<span style="font-weight: 700">foo[bar]baz</span>',
-	'<span style="font-weight: 900">foo[bar]baz</span>',
-	'<span style="font-weight: normal">foo[bar]baz</span>',
-	'{<span style="font-weight: 100">foobar]baz</span>',
-	'{<span style="font-weight: 400">foobar]baz</span>',
-	'{<span style="font-weight: 700">foobar]baz</span>',
-	'{<span style="font-weight: 900">foobar]baz</span>',
-	'{<span style="font-weight: normal">foobar]baz</span>',
-	'<span style="font-weight: 100">foo[barbaz</span>}',
-	'<span style="font-weight: 400">foo[barbaz</span>}',
-	'<span style="font-weight: 700">foo[barbaz</span>}',
-	'<span style="font-weight: 900">foo[barbaz</span>}',
-	'<span style="font-weight: normal">foo[barbaz</span>}',
-	'<h3>Foo[bar]baz</h3>',
-	'{<h3>Foobar]baz</h3>',
-	'<h3>Foo[barbaz</h3>}',
-	'<h3>[Foobarbaz]</h3>',
-	'{<h3>Foobarbaz]</h3>',
-	'<h3>[Foobarbaz</h3>}',
-	'{<h3>Foobarbaz</h3>}',
-	'<b>Foo<span style="font-weight: normal">bar<b>[baz]</b>quz</span>qoz</b>',
-	'<b>Foo<span style="font-weight: normal">[bar]</span>baz</b>',
-	'{<b>Foo</b> <b>bar</b>}',
-	'{<h3>Foo</h3><b>bar</b>}',
-	'<i><b>Foo</b></i>[bar]<i><b>baz</b></i>',
-	'<i><b>Foo</b></i>[bar]<b>baz</b>',
-	'<b>Foo</b>[bar]<i><b>baz</b></i>',
-	'{<p><p> <p>Foo</p>}',
-	'[Foo<span class=notbold>bar</span>baz]',
-];
+var tests = {
+	bold: [
+		'foo[bar]baz',
+		'foo]bar[baz',
+		'{<p><p> <p>Foo</p>}',
+		'foo[bar<i>baz]qoz</i>quz',
+		'foo<span style="font-weight: bold">[bar]</span>baz',
+		'foo<b>[bar]</b>baz',
+		'foo[<b>bar</b>]baz',
+		'foo[<b>bar]</b>baz',
+		'foo<b>[bar</b>]baz',
+		'foo<strong>[bar]</strong>baz',
+		'foo[<strong>bar</strong>]baz',
+		'foo[<strong>bar]</strong>baz',
+		'foo<strong>[bar</strong>]baz',
+		'foo<span style="font-weight: bold">[bar]</span>baz',
+		'foo[<span style="font-weight: bold">bar</span>]baz',
+		'foo[<span style="font-weight: bold">bar]</span>baz',
+		'foo<span style="font-weight: bold">[bar</span>]baz',
+		'<b>{<p>foo</p><p>bar</p>}<p>baz</p></b>',
+		'<b><p>foo[<i>bar</i>}</p><p>baz</p></b>',
+		'foo [bar <b>baz] qoz</b> quz sic',
+		'foo bar <b>baz [qoz</b> quz] sic',
+		'<b id=purple>bar [baz] qoz</b>',
+		'foo<span style="font-weight: 100">[bar]</span>baz',
+		'foo<span style="font-weight: 400">[bar]</span>baz',
+		'foo<span style="font-weight: 700">[bar]</span>baz',
+		'foo<span style="font-weight: 900">[bar]</span>baz',
+		'foo<span style="font-weight: 400">[bar</span>]baz',
+		'foo<span style="font-weight: 700">[bar</span>]baz',
+		'foo[<span style="font-weight: 400">bar]</span>baz',
+		'foo[<span style="font-weight: 700">bar]</span>baz',
+		'foo[<span style="font-weight: 400">bar</span>]baz',
+		'foo[<span style="font-weight: 700">bar</span>]baz',
+		'<span style="font-weight: 100">foo[bar]baz</span>',
+		'<span style="font-weight: 400">foo[bar]baz</span>',
+		'<span style="font-weight: 700">foo[bar]baz</span>',
+		'<span style="font-weight: 900">foo[bar]baz</span>',
+		'{<span style="font-weight: 100">foobar]baz</span>',
+		'{<span style="font-weight: 400">foobar]baz</span>',
+		'{<span style="font-weight: 700">foobar]baz</span>',
+		'{<span style="font-weight: 900">foobar]baz</span>',
+		'<span style="font-weight: 100">foo[barbaz</span>}',
+		'<span style="font-weight: 400">foo[barbaz</span>}',
+		'<span style="font-weight: 700">foo[barbaz</span>}',
+		'<span style="font-weight: 900">foo[barbaz</span>}',
+		'<h3>Foo[bar]baz</h3>',
+		'{<h3>Foobar]baz</h3>',
+		'<h3>Foo[barbaz</h3>}',
+		'<h3>[Foobarbaz]</h3>',
+		'{<h3>Foobarbaz]</h3>',
+		'<h3>[Foobarbaz</h3>}',
+		'{<h3>Foobarbaz</h3>}',
+		'<b>Foo<span style="font-weight: normal">bar<b>[baz]</b>quz</span>qoz</b>',
+		'<b>Foo<span style="font-weight: normal">[bar]</span>baz</b>',
+		'{<b>Foo</b> <b>bar</b>}',
+		'{<h3>Foo</h3><b>bar</b>}',
+		'<i><b>Foo</b></i>[bar]<i><b>baz</b></i>',
+		'<i><b>Foo</b></i>[bar]<b>baz</b>',
+		'<b>Foo</b>[bar]<i><b>baz</b></i>',
+		'[Foo<span class=notbold>bar</span>baz]',
+	],
+	fontname: [
+		'foo[bar]baz',
+		'foo]bar[baz',
+		'{<p><p> <p>Foo</p>}',
 
-for (var i = 0; i < tests.length; i++) {
-	addTest(tests[i]);
+		'foo<code>[bar]</code>baz',
+		'foo<kbd>[bar]</kbd>baz',
+		'foo<listing>[bar]</listing>baz',
+		'foo<pre>[bar]</pre>baz',
+		'foo<samp>[bar]</samp>baz',
+		'foo<tt>[bar]</tt>baz',
+
+		'foo<code>b[a]r</code>baz',
+		'foo<kbd>b[a]r</kbd>baz',
+		'foo<listing>b[a]r</listing>baz',
+		'foo<pre>b[a]r</pre>baz',
+		'foo<samp>b[a]r</samp>baz',
+		'foo<tt>b[a]r</tt>baz',
+
+		'[foo<code>bar</code>baz]',
+		'[foo<kbd>bar</kbd>baz]',
+		'[foo<listing>bar</listing>baz]',
+		'[foo<pre>bar</pre>baz]',
+		'[foo<samp>bar</samp>baz]',
+		'[foo<tt>bar</tt>baz]',
+
+		'[foo<code>ba]r</code>baz',
+		'[foo<kbd>ba]r</kbd>baz',
+		'[foo<listing>ba]r</listing>baz',
+		'[foo<pre>ba]r</pre>baz',
+		'[foo<samp>ba]r</samp>baz',
+		'[foo<tt>ba]r</tt>baz',
+
+		'foo<code>b[ar</code>baz]',
+		'foo<kbd>b[ar</kbd>baz]',
+		'foo<listing>b[ar</listing>baz]',
+		'foo<pre>b[ar</pre>baz]',
+		'foo<samp>b[ar</samp>baz]',
+		'foo<tt>b[ar</tt>baz]',
+
+		'foo<span style="font-family: serif">[bar]</span>baz',
+		'foo<span style="font-family: serif">b[a]r</span>baz',
+		'foo<span style="font-family: monospace">[bar]</span>baz',
+		'foo<span style="font-family: monospace">b[a]r</span>baz',
+	],
+	forecolor: [
+		'foo[bar]baz',
+		'foo]bar[baz',
+		'{<p><p> <p>Foo</p>}',
+		'foo[bar<i>baz]qoz</i>quz',
+		'foo<font color=red>[bar]</font>baz',
+		'foo{<font color=red>bar</font>}baz',
+		'<span style="color: red">foo<span style="color: blue">[bar]</span>baz</span>',
+		'<span style="color: #f00">foo<span style="color: blue">[bar]</span>baz</span>',
+		'<span style="color: #ff0000">foo<span style="color: blue">[bar]</span>baz</span>',
+		'<span style="color: rgb(255, 0, 0)">foo<span style="color: blue">[bar]</span>baz</span>',
+		'<font color=red>foo<font color=blue>[bar]</font>baz</font>',
+		'<span style="color: rgb(255, 0, 0)">foo<span style="color: blue">b[ar]</span>baz</span>',
+		'foo<span id=purple>ba[r</span>ba]z',
+		'<span style="color: rgb(255, 0, 0)">foo<span id=purple>b[a]r</span>baz</span>',
+	],
+	italic: [
+		'foo[bar]baz',
+		'foo]bar[baz',
+		'{<p><p> <p>Foo</p>}',
+		'foo[bar<b>baz]qoz</b>quz',
+		'foo<span style="font-style: italic">[bar]</span>baz',
+		'foo<address>[bar]</address>baz',
+		'foo<cite>[bar]</cite>baz',
+		'foo<dfn>[bar]</dfn>baz',
+		'foo<em>[bar]</em>baz',
+		'foo<i>[bar]</i>baz',
+		'foo<var>[bar]</var>baz',
+		'foo{<address>bar</address>}baz',
+		'foo{<cite>bar</cite>}baz',
+		'foo{<dfn>bar</dfn>}baz',
+		'foo{<em>bar</em>}baz',
+		'foo{<i>bar</i>}baz',
+		'foo{<var>bar</var>}baz',
+		'foo<address>b[a]r</address>baz',
+		'foo<cite>b[a]r</cite>baz',
+		'foo<dfn>b[a]r</dfn>baz',
+		'foo<em>b[a]r</em>baz',
+		'foo<i>b[a]r</i>baz',
+		'foo<var>b[a]r</var>baz',
+		'fo[o<address>bar</address>b]az',
+		'fo[o<cite>bar</cite>b]az',
+		'fo[o<dfn>bar</dfn>b]az',
+		'fo[o<em>bar</em>b]az',
+		'fo[o<i>bar</i>b]az',
+		'fo[o<var>bar</var>b]az',
+		'foo[<address>bar</address>baz]',
+		'foo[<cite>bar</cite>baz]',
+		'foo[<dfn>bar</dfn>baz]',
+		'foo[<em>bar</em>baz]',
+		'foo[<i>bar</i>baz]',
+		'foo[<var>bar</var>baz]',
+		'[foo<address>bar</address>]baz',
+		'[foo<cite>bar</cite>]baz',
+		'[foo<dfn>bar</dfn>]baz',
+		'[foo<em>bar</em>]baz',
+		'[foo<i>bar</i>]baz',
+		'[foo<var>bar</var>]baz',
+		'foo<span style="font-style: italic">[bar]</span>baz',
+		'foo<span style="font-style: oblique">[bar]</span>baz',
+		'foo<span style="font-style: oblique">b[a]r</span>baz',
+		'<i>{<p>foo</p><p>bar</p>}<p>baz</p></i>',
+		'<i><p>foo[<b>bar</b>}</p><p>baz</p></i>',
+		'foo [bar <b>baz] qoz</b> quz sic',
+		'foo bar <b>baz [qoz</b> quz] sic',
+		'foo [bar <i>baz] qoz</i> quz sic',
+		'foo bar <i>baz [qoz</i> quz] sic',
+	],
+};
+
+for (testCat in tests) {
+	for (var i = 0; i < tests[testCat].length; i++) {
+		addTest(testCat, tests[testCat][i]);
+	}
 }
 
-function addTest(test) {
-	document.querySelector("div").contentEditable = "true";
-	var table = document.querySelector("table");
+function addTest(command, test) {
+	var value = {
+		bold: null,
+		fontname: "serif",
+		forecolor: "#FF0000",
+		italic: null,
+	}[command];
+	var styleWithCss = {
+		bold: false,
+		fontname: true,
+		forecolor: true,
+		italic: false,
+	}[command];
+
+	var div = document.getElementById(command);
+	div.contentEditable = "true";
+	var table = div.querySelector("table");
 
 	var tr = document.createElement("tr");
 	// Insert at the top, because Chrome debugger doesn't let you scroll down
@@ -128,28 +290,40 @@
 	tr.appendChild(inputCell);
 
 	var specCell = document.createElement("td");
-	specCell.innerHTML = test;
+	specCell.appendChild(document.createElement("div"));
+	specCell.appendChild(document.createElement("div"));
+	specCell.firstChild.innerHTML = test;
 	tr.appendChild(specCell);
 	try {
-		selectBrackets(specCell);
-		myExecCommand("bold");
-		specCell.innerHTML = "<div>" + specCell.innerHTML + "</div><div>" + specCell.innerHTML.replace(/\&/g, "&amp;").replace(/</g, "&lt;") + "</div>";
+		var points = parseBrackets(specCell);
+		var range = document.createRange();
+		range.setStart(points[0], points[1]);
+		range.setEnd(points[2], points[3]);
+		// The points might be backwards
+		if (range.collapsed) {
+			range.setEnd(points[0], points[1]);
+		}
+		myExecCommand(command, false, value, range);
+		specCell.lastChild.textContent = specCell.firstChild.innerHTML;
 	} catch (e) {
 		specCell.textContent = "Exception: " + e;
 	}
 
 	var browserCell = document.createElement("td");
-	browserCell.innerHTML = test;
+	browserCell.appendChild(document.createElement("div"));
+	browserCell.appendChild(document.createElement("div"));
+	browserCell.firstChild.innerHTML = test;
 	tr.appendChild(browserCell);
 	try {
-		selectBrackets(browserCell);
+		var points = parseBrackets(browserCell);
+		setSelection(points[0], points[1], points[2], points[3]);
 		try {
-			document.execCommand("styleWithCSS", null, false);
+			document.execCommand("styleWithCss", false, styleWithCss);
 		} catch (e) {
 			// IE, we don't care
 		}
-		document.execCommand("bold", null, false);
-		browserCell.innerHTML = "<div>" + browserCell.innerHTML + "</div><div>" + browserCell.innerHTML.replace(/\&/g, "&amp;").replace(/</g, "&lt;") + "</div>";
+		document.execCommand(command, false, value);
+		browserCell.lastChild.textContent = browserCell.innerHTML;
 	} catch (e) {
 		browserCell.textContent = "Exception: " + e;
 	}
@@ -160,6 +334,8 @@
 		.replace(/;? ?"/g, '"');
 	var normalizedBrowserCell = browserCell.lastChild.textContent
 		.replace(/;? ?"/g, '"')
+		.replace(/<(\/?)strong/g, '<$1b')
+		.replace(/<(\/?)em/g, '<$1i')
 		.replace(/ class="Apple-style-span"/g, "");
 	if (normalizedSpecCell == normalizedBrowserCell) {
 		sameCell.className = "yes";
@@ -171,10 +347,10 @@
 	tr.appendChild(sameCell);
 
 	getSelection().removeAllRanges();
-	document.querySelector("div").contentEditable = "inherit";
+	div.contentEditable = "inherit";
 }
 
-function selectBrackets(node) {
+function parseBrackets(node) {
 	if (node.textContent.replace(/[^{[]/g, "").length != 1) {
 		throw "Need one [ or {, found " + node.textContent.replace(/[^{[]/g, "").length;
 	}
@@ -249,6 +425,10 @@
 		}
 	}
 
+	return [startNode, startOffset, endNode, endOffset];
+}
+
+function setSelection(startNode, startOffset, endNode, endOffset) {
 	if (navigator.userAgent.indexOf("Opera") != -1) {
 		// Yes, browser sniffing is evil, but I can't be bothered to debug
 		// Opera.
--- a/editcommands.html	Thu Mar 17 16:14:25 2011 -0600
+++ b/editcommands.html	Fri Mar 18 16:14:18 2011 -0600
@@ -27,7 +27,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-17-march-2011>Work in Progress &mdash; Last Update 17 March 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-18-march-2011>Work in Progress &mdash; Last Update 18 March 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;ayg+spec@aryeh.name&gt;
@@ -306,7 +306,7 @@
   <li>If <var title="">start node</var> is a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a> node and is the same as <var title="">end
   node</var>, and <var title="">start offset</var> is neither 0 nor 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="">start node</var>:
-  
+
   <ol>
     <li>Set <var title="">start node</var> to the result of running <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-text-splittext><code class=external data-anolis-spec=domcore title=dom-Text-splitText>splitText(<var title="">start
     offset</var>)</code></a> on <var title="">start node</var>.
@@ -543,6 +543,25 @@
 After forcing the style, descendants might still have different style.
 
 <ol>
+  <!-- Even if the style matches, we stick it in a preceding sibling if
+  possible.  This ensures "a<cite>b</cite>c" -> "<i>a<cite>b</cite>c</i>"
+  instead of "<i>a</i><cite>b</cite><i>c</i>". -->
+  <li>If <var title="">node</var> is an <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a>, <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text><code class=external data-anolis-spec=domcore>Text</code></a>, <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment><code class=external data-anolis-spec=domcore>Comment</code></a>, or
+  <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction><code class=external data-anolis-spec=domcore>ProcessingInstruction</code></a> node, and is not an <a href=#unwrappable-element>unwrappable
+  element</a>:
+
+  <ol>
+    <li>If <var title="">node</var>'s <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling><code class=external data-anolis-spec=domcore title=dom-Node-previousSibling>previousSibling</code></a> is a <a href=#simple-styling-element>simple styling
+    element</a> whose <a href=#specified-style>specified style</a> and computed style for
+    <var title="">property</var> are both <var title="">new value</var>, append <var title="">node</var>
+    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 its <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling><code class=external data-anolis-spec=domcore title=dom-Node-previousSibling>previousSibling</code></a> and abort this algorithm.
+
+    <li>If <var title="">node</var>'s <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling><code class=external data-anolis-spec=domcore title=dom-Node-nextSibling>nextSibling</code></a> is a <a href=#simple-styling-element>simple styling
+    element</a> whose <a href=#specified-style>specified style</a> and computed style for
+    <var title="">property</var> are both <var title="">new value</var>, insert <var title="">node</var>
+    as the first <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 its <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling><code class=external data-anolis-spec=domcore title=dom-Node-nextSibling>nextSibling</code></a> and abort this algorithm.
+  </ol>
+
   <li>If <var title="">node</var> is an <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> and <var title="">property</var> computes to
   <var title="">new value</var> on <var title="">node</var>, abort this algorithm.
 
@@ -564,16 +583,6 @@
     <li>Abort this algorithm.
   </ol>
 
-  <li>If <var title="">node</var>'s <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling><code class=external data-anolis-spec=domcore title=dom-Node-previousSibling>previousSibling</code></a> is a <a href=#simple-styling-element>simple styling
-  element</a> whose <a href=#specified-style>specified style</a> and computed style for
-  <var title="">property</var> are both <var title="">new value</var>, append <var title="">node</var> 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 its <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling><code class=external data-anolis-spec=domcore title=dom-Node-previousSibling>previousSibling</code></a> and abort this algorithm.
-
-  <li>If <var title="">node</var>'s <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling><code class=external data-anolis-spec=domcore title=dom-Node-nextSibling>nextSibling</code></a> is a <a href=#simple-styling-element>simple styling
-  element</a> whose <a href=#specified-style>specified style</a> and computed style for
-  <var title="">property</var> are both <var title="">new value</var>, insert <var title="">node</var> as
-  the first <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 its <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling><code class=external data-anolis-spec=domcore title=dom-Node-nextSibling>nextSibling</code></a> and abort this algorithm.
-
   <!-- At this point we have to make a new element as a wrapper.  This isn't
   worth the effort for comments or PIs, so abort in that case. -->
   <li>If <var title="">node</var> is a <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment><code class=external data-anolis-spec=domcore>Comment</code></a> or <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#processinginstruction><code class=external data-anolis-spec=domcore>ProcessingInstruction</code></a>, abort
@@ -610,8 +619,17 @@
   <li>Append <var title="">node</var> to <var title="">new parent</var> as its 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>.
 
   <li>If <var title="">node</var> is an <a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element><code class=external data-anolis-spec=domcore>Element</code></a> and the computed value of
-  <var title="">property</var> for <var title="">node</var> is not <var title="">new value</var>, set the
-  CSS property <var title="">property</var> of <var title="">node</var> to <var title="">new value</var>.
+  <var title="">property</var> for <var title="">node</var> is not <var title="">new value</var>:
+
+  <ol>
+    <li>Set the CSS property <var title="">property</var> of <var title="">node</var> to <var title="">new
+    value</var>.
+
+    <li>Insert <var title="">node</var> into the <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> of <var title="">new parent</var>
+    before <var title="">new parent</var>.
+
+    <li>Remove <var title="">new parent</var> 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>.
+  </ol>
 </ol>
 
 
--- a/implementation.js	Thu Mar 17 16:14:25 2011 -0600
+++ b/implementation.js	Fri Mar 18 16:14:18 2011 -0600
@@ -265,6 +265,7 @@
 		"h1", "h2", "h3", "h4", "h5", "h6", "p", "hr", "pre", "blockquote",
 		"ol", "ul", "li", "dl", "dt", "dd", "div", "table", "caption",
 		"colgroup", "col", "tbody", "thead", "tfoot", "tr", "th", "td",
+		"address"
 	].indexOf(node.tagName.toLowerCase()) != -1;
 }
 
@@ -700,6 +701,38 @@
 }
 
 function forceStyle(node, property, newValue) {
+	// "If node is an Element, Text, Comment, or ProcessingInstruction node,
+	// and is not an unwrappable element:"
+	if ((node.nodeType == Node.ELEMENT_NODE
+	|| node.nodeType == Node.TEXT_NODE
+	|| node.nodeType == Node.COMMENT_NODE
+	|| node.nodeType == Node.PROCESSING_INSTRUCTION_NODE)
+	&& !isUnwrappableElement(node)) {
+		// "If node's previousSibling is a simple styling element whose
+		// specified style and computed style for property are both new value,
+		// append node as the last child of its previousSibling and abort this
+		// algorithm."
+		if (isSimpleStylingElement(node.previousSibling)
+		&& cssValuesEqual(property, getSpecifiedStyle(node.previousSibling, property), newValue)
+		&& cssValuesEqual(property, getComputedStyle(node.previousSibling)[property], newValue)) {
+			node.previousSibling.appendChild(node);
+			return;
+		}
+
+		// "If node's nextSibling is a simple styling element whose specified
+		// style and computed style for property are both new value, insert
+		// node as the first child of its nextSibling and abort this
+		// algorithm."
+		if (isSimpleStylingElement(node.nextSibling)
+		&& cssValuesEqual(property, getSpecifiedStyle(node.nextSibling, property), newValue)
+		&& cssValuesEqual(property, getComputedStyle(node.nextSibling)[property], newValue)) {
+			node.nextSibling.insertBefore(node, node.nextSibling.childNodes.length
+				? node.nextSibling.childNodes[0]
+				: null);
+			return;
+		}
+	}
+
 	// "If node is an Element and property computes to new value on node, abort
 	// this algorithm."
 	if (node.nodeType == Node.ELEMENT_NODE
@@ -743,28 +776,6 @@
 		return;
 	}
 
-	// "If node's previousSibling is a simple styling element whose specified
-	// style and computed style for property are both new value, append node as
-	// the last child of its previousSibling and abort this algorithm."
-	if (isSimpleStylingElement(node.previousSibling)
-	&& cssValuesEqual(property, getSpecifiedStyle(node.previousSibling, property), newValue)
-	&& cssValuesEqual(property, getComputedStyle(node.previousSibling)[property], newValue)) {
-		node.previousSibling.appendChild(node);
-		return;
-	}
-
-	// "If node's nextSibling is a simple styling element whose specified style
-	// and computed style for property are both new value, insert node as the
-	// first child of its nextSibling and abort this algorithm."
-	if (isSimpleStylingElement(node.nextSibling)
-	&& cssValuesEqual(property, getSpecifiedStyle(node.nextSibling, property), newValue)
-	&& cssValuesEqual(property, getComputedStyle(node.nextSibling)[property], newValue)) {
-		node.nextSibling.insertBefore(node, node.nextSibling.childNodes.length
-			? node.nextSibling.childNodes[0]
-			: null);
-		return;
-	}
-
 	// "If node is a Comment or ProcessingInstruction, abort this algorithm."
 	if (node.nodeType == Node.COMMENT_NODE
 	|| node.nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
@@ -788,7 +799,7 @@
 
 	// "If property is "font-weight" and new value is "bold", let tag be "b"."
 	var tag;
-	if (property == "fontWeight" && newValue == "bold") {
+	if (property == "fontWeight" && (newValue == "bold" || newValue == "700")) {
 		tag = "b";
 	// "If property is "font-style" and new value is "italic", let tag be "i"."
 	} else if (property == "fontStyle" && newValue == "italic") {
@@ -819,10 +830,17 @@
 	newParent.appendChild(node);
 
 	// "If node is an Element and the computed value of property for node is
-	// not new value, set the CSS property property of node to new value."
+	// not new value:"
 	if (node.nodeType == Node.ELEMENT_NODE
 	&& !cssValuesEqual(property, getComputedStyle(node)[property], newValue)) {
+		// "Set the CSS property property of node to new value."
 		node.style[property] = newValue;
+
+		// "Insert node into the parent of new parent before new parent."
+		newParent.parentNode.insertBefore(node, newParent);
+
+		// "Remove new parent from its parent."
+		newParent.parentNode.removeChild(newParent);
 	}
 }
 
@@ -893,9 +911,12 @@
 	}
 }
 
-function myExecCommand(commandId, showUI, value) {
+function myExecCommand(commandId, showUI, value, range) {
 	commandId = commandId.toLowerCase();
-	var range = getActiveRange(document);
+
+	if (typeof range == "undefined") {
+		range = getActiveRange(document);
+	}
 
 	if (!range) {
 		return;
@@ -991,6 +1012,28 @@
 		}
 		*/
 
+		case "fontname":
+		// "Decompose the Range, then style each returned Node with property
+		// equal to "font-family" and new value equal to value."
+		var nodeList = decomposeRange(range);
+		for (var i = 0; i < nodeList.length; i++) {
+			styleNode(nodeList[i], "fontFamily", value);
+		}
+		break;
+
+		case "forecolor":
+		// "If value is not a valid CSS color, the user agent must do nothing
+		// and abort these steps. Otherwise, it must decompose the Range, then
+		// style each returned Node with property equal to "color" and new
+		// value equal to value."
+		//
+		// Ignore validation for now.
+		var nodeList = decomposeRange(range);
+		for (var i = 0; i < nodeList.length; i++) {
+			styleNode(nodeList[i], "color", value);
+		}
+		break;
+
 		case "italic":
 		// "Decompose the Range. If the state of the Range for this command is
 		// then true, style each returned Node with property "font-style" and
--- a/source.html	Thu Mar 17 16:14:25 2011 -0600
+++ b/source.html	Fri Mar 18 16:14:18 2011 -0600
@@ -297,7 +297,7 @@
   <li>If <var>start node</var> is a [[text]] node and is the same as <var>end
   node</var>, and <var>start offset</var> is neither 0 nor the [[nodelength]]
   of <var>start node</var>:
-  
+
   <ol>
     <li>Set <var>start node</var> to the result of running <code
     data-anolis-spec=domcore title=dom-Text-splitText>splitText(<var>start
@@ -539,6 +539,25 @@
 After forcing the style, descendants might still have different style.
 
 <ol>
+  <!-- Even if the style matches, we stick it in a preceding sibling if
+  possible.  This ensures "a<cite>b</cite>c" -> "<i>a<cite>b</cite>c</i>"
+  instead of "<i>a</i><cite>b</cite><i>c</i>". -->
+  <li>If <var>node</var> is an [[element]], [[text]], [[comment]], or
+  [[processinginstruction]] node, and is not an <span>unwrappable
+  element</span>:
+
+  <ol>
+    <li>If <var>node</var>'s [[previoussibling]] is a <span>simple styling
+    element</span> whose <span>specified style</span> and computed style for
+    <var>property</var> are both <var>new value</var>, append <var>node</var>
+    as the last [[child]] of its [[previoussibling]] and abort this algorithm.
+
+    <li>If <var>node</var>'s [[nextsibling]] is a <span>simple styling
+    element</span> whose <span>specified style</span> and computed style for
+    <var>property</var> are both <var>new value</var>, insert <var>node</var>
+    as the first [[child]] of its [[nextsibling]] and abort this algorithm.
+  </ol>
+
   <li>If <var>node</var> is an [[element]] and <var>property</var> computes to
   <var>new value</var> on <var>node</var>, abort this algorithm.
 
@@ -560,16 +579,6 @@
     <li>Abort this algorithm.
   </ol>
 
-  <li>If <var>node</var>'s [[previoussibling]] is a <span>simple styling
-  element</span> whose <span>specified style</span> and computed style for
-  <var>property</var> are both <var>new value</var>, append <var>node</var> as
-  the last [[child]] of its [[previoussibling]] and abort this algorithm.
-
-  <li>If <var>node</var>'s [[nextsibling]] is a <span>simple styling
-  element</span> whose <span>specified style</span> and computed style for
-  <var>property</var> are both <var>new value</var>, insert <var>node</var> as
-  the first [[child]] of its [[nextsibling]] and abort this algorithm.
-
   <!-- At this point we have to make a new element as a wrapper.  This isn't
   worth the effort for comments or PIs, so abort in that case. -->
   <li>If <var>node</var> is a [[comment]] or [[processinginstruction]], abort
@@ -608,8 +617,17 @@
   <li>Append <var>node</var> to <var>new parent</var> as its last [[child]].
 
   <li>If <var>node</var> is an [[element]] and the computed value of
-  <var>property</var> for <var>node</var> is not <var>new value</var>, set the
-  CSS property <var>property</var> of <var>node</var> to <var>new value</var>.
+  <var>property</var> for <var>node</var> is not <var>new value</var>:
+
+  <ol>
+    <li>Set the CSS property <var>property</var> of <var>node</var> to <var>new
+    value</var>.
+
+    <li>Insert <var>node</var> into the [[parent]] of <var>new parent</var>
+    before <var>new parent</var>.
+
+    <li>Remove <var>new parent</var> from its [[parent]].
+  </ol>
 </ol>