Start on indent support
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Thu, 14 Apr 2011 10:11:40 -0600
changeset 69 bcd801b33f32
parent 68 db8298c35b46
child 70 471457773bcd
Start on indent support

I'll stop and move back to DOM Range to define surroundContents() and
insertNode(), because I suspect they'll be useful here.
autoimplementation.html
editcommands.html
source.html
--- a/autoimplementation.html	Wed Apr 13 14:01:04 2011 -0600
+++ b/autoimplementation.html	Thu Apr 14 10:11:40 2011 -0600
@@ -1,4 +1,5 @@
 <!doctype html>
+<meta charset=utf-8>
 <title>Auto-running execCommand() tests</title>
 <style>
 body { font-family: serif }
@@ -431,6 +432,57 @@
 		'<div style="background-color: #ff8888"><p style="background-color: aqua">b[ar]</p></div>',
 		'<span style="display: block; background-color: #ff8888"><span style="display: block; background-color: aqua">b[ar]</span></span>',
 	],
+	indent: [
+		// All these have a trailing unselected paragraph, because otherwise
+		// Gecko is unhappy: it throws exceptions in non-CSS mode, and in CSS
+		// mode it adds the indentation invisibly to the wrapper div in many
+		// cases.
+		'foo[]bar<p>extra',
+		'<span>foo</span>{}<span>bar</span><p>extra',
+		'<span>foo[</span><span>]bar</span><p>extra',
+		'foo[bar]baz<p>extra',
+		'<p dir=rtl>פו[בר]בז<p dir=rtl>נוםף',
+		'foo]bar[baz<p>extra',
+		'{<p><p> <p>foo</p>}<p>extra',
+		'foo[bar<i>baz]qoz</i>quz<p>extra',
+
+		'<table><tbody><tr><td>foo<td>b[a]r<td>baz</table><p>extra',
+		'<table><tbody><tr data-start=1 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody><tr data-start=0 data-end=2><td>foo<td>bar<td>baz</table><p>extra',
+		'<table><tbody data-start=0 data-end=1><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'<table data-start=0 data-end=1><tbody><tr><td>foo<td>bar<td>baz</table><p>extra',
+		'{<table><tr><td>foo<td>bar<td>baz</table>}<p>extra',
+
+		'<p>foo[bar]</p><p>baz</p><p>extra',
+		'<p>[foobar</p><p>ba]z</p><p>extra',
+		'foo[bar]<br>baz<p>extra',
+		'foo[bar<br>ba]z<p>extra',
+
+		// These mimic existing indentation in various browsers, to see how
+		// they cope with indenting twice.  This is Gecko non-CSS and Opera:
+		'<blockquote><p>foo[bar]</p><p>baz</p></blockquote><p>extra',
+		'<blockquote><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
+		'<blockquote><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
+		'<blockquote><p>foo[bar</p></blockquote><p>b]az</p><p>extra',
+
+		// IE:
+		'<blockquote style="margin-right: 0" dir="ltr"><p>foo[bar]</p><p>baz</p></blockquote><p>extra',
+		'<blockquote style="margin-right: 0" dir="ltr"><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
+		'<blockquote style="margin-right: 0" dir="ltr"><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
+		'<blockquote style="margin-right: 0" dir="ltr"><p>foo[bar</p></blockquote><p>b]az</p><p>extra',
+
+		// Firefox CSS mode:
+		'<p style="margin-left: 40px">foo[bar]</p><p style="margin-left: 40px">baz</p><p>extra',
+		'<p style="margin-left: 40px">foo[bar</p><p style="margin-left: 40px">b]az</p><p>extra',
+		'<p style="margin-left: 40px">foo[bar]</p><p>baz</p><p>extra',
+		'<p style="margin-left: 40px">foo[bar</p><p>b]az</p><p>extra',
+
+		// WebKit:
+		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar]</p><p>baz</p></blockquote><p>extra',
+		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
+		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
+		'<blockquote class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px"><p>foo[bar</p></blockquote><p>b]az</p><p>extra',
+	],
 	inserthorizontalrule: [
 		'foo[]bar',
 		'<span>foo</span>{}<span>bar</span>',
@@ -904,9 +956,19 @@
 	hilitecolor: 'In IE we run backColor instead of hiliteColor.',
 };
 
-var doubleTestingCommands = ["backcolor", "bold", "italic", "strikethrough",
-	"underline", "forecolor", "fontname", "fontsize", "subscript",
-	"superscript"];
+var doubleTestingCommands = [
+	"backcolor",
+	"bold",
+	"fontname",
+	"fontsize",
+	"forecolor",
+	"indent",
+	"italic",
+	"strikethrough",
+	"subscript",
+	"superscript",
+	"underline",
+];
 
 // Set up all the HTML automatically so I can add new commands to test without
 // copy-pasting in five places
@@ -994,18 +1056,10 @@
 }
 
 function doSetup(command, idx) {
-	document.getElementById(command).contentEditable = "true";
-	document.getElementById(command).spellcheck = false;
 	var table = document.querySelectorAll("#" + command + " > table")[idx];
 
 	var tr = document.createElement("tr");
-	// Insert at the top, because Chrome debugger doesn't let you scroll down
-	// while you're stopped at a breakpoint . . .
-	//if (table.childNodes.length == 1) {
-		table.appendChild(tr);
-	//} else {
-	//	table.insertBefore(tr, table.childNodes[1]);
-	//}
+	table.appendChild(tr);
 
 	return tr;
 }
@@ -1045,8 +1099,12 @@
 		if (range.collapsed) {
 			range.setEnd(points[0], points[1]);
 		}
+		specCell.firstChild.contentEditable = "true";
+		specCell.firstChild.spellcheck = false;
 		myExecCommand("styleWithCSS", false, styleWithCss);
 		myExecCommand(command, false, value, range);
+		specCell.firstChild.contentEditable = "inherit";
+		specCell.firstChild.removeAttribute("spellcheck");
 		addBrackets(range);
 		if (specCell.childNodes.length == 2) {
 			specCell.lastChild.textContent = specCell.firstChild.innerHTML;
@@ -1054,6 +1112,8 @@
 			throw "The cell didn't have two children.  Did something spill outside the test div?";
 		}
 	} catch (e) {
+		specCell.firstChild.contentEditable = "inherit";
+		specCell.firstChild.removeAttribute("spellcheck");
 		specCell.lastChild.style.color = "red";
 		specCell.lastChild.style.fontWeight = "bold";
 		specCell.lastChild.textContent = "Note, exception: " + e;
@@ -1096,32 +1156,36 @@
 	tr.appendChild(browserCell);
 	try {
 		var points = setupCell(browserCell, test);
+
 		var testDiv = browserCell.firstChild;
 		// Work around weird Firefox bug:
 		// https://bugzilla.mozilla.org/show_bug.cgi?id=649138
-		// But only with removeFormat, because it tends to cause weird behavior
-		// sometimes if content winds up being put outside the div for some
-		// reason: then it's mysteriously below the table.
-		if (command == "removeformat") {
-			document.getElementById(command).appendChild(testDiv);
-		}
+		document.body.appendChild(testDiv);
 		setSelection(points[0], points[1], points[2], points[3]);
+		testDiv.contentEditable = "true";
+		testDiv.spellcheck = false;
+
 		try {
 			document.execCommand("styleWithCSS", false, styleWithCss);
 		} catch (e) {}
 		document.execCommand(command, false, value);
+
+		testDiv.contentEditable = "inherit";
+		testDiv.removeAttribute("spellcheck");
 		if (getSelection().rangeCount) {
 			addBrackets(getSelection().getRangeAt(0));
 		}
-		if (testDiv.parentNode != browserCell) {
-			browserCell.insertBefore(testDiv, browserCell.firstChild);
-		}
+		browserCell.insertBefore(testDiv, browserCell.firstChild);
 		if (browserCell.childNodes.length == 2) {
 			browserCell.lastChild.textContent = browserCell.firstChild.innerHTML;
 		} else {
 			throw "The cell didn't have two children.  Did something spill outside the test div?";
 		}
 	} catch (e) {
+		if (testDiv) {
+			testDiv.contenteditable = "inherit";
+			testDiv.removeAttribute("spellcheck");
+		}
 		browserCell.lastChild.style.color = "red";
 		browserCell.lastChild.style.fontWeight = "bold";
 		browserCell.lastChild.textContent = "Exception: " + e;
@@ -1190,8 +1254,6 @@
 
 function doTearDown(command) {
 	getSelection().removeAllRanges();
-	document.getElementById(command).contentEditable = "inherit";
-	document.getElementById(command).removeAttribute("spellcheck");
 }
 
 function setupCell(cell, test) {
--- a/editcommands.html	Wed Apr 13 14:01:04 2011 -0600
+++ b/editcommands.html	Thu Apr 14 10:11:40 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-13-april-2011>Work in Progress &mdash; Last Update 13 April 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-14-april-2011>Work in Progress &mdash; Last Update 14 April 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;ayg+spec@aryeh.name&gt;
@@ -1681,6 +1681,97 @@
 <dd><strong>Relevant CSS Property</strong>: "background-color"
 
 
+<!--
+<dt><code title><dfn title=command-indent>indent</dfn></code>
+-->
+<!--
+IE9: Outputs <blockquote style="margin-right: 0px" dir="ltr">, or when
+  surrounding RTL blocks, <blockquote style="margin-left: 0px" dir="rtl">.
+Firefox 4.0: In styleWithCSS mode, adds style="margin-left: 40px" to the
+  appropriate block container (or margin-right if it's RTL).  If there's no
+  appropriate block container, adds a div.  In non-styleWithCSS mode, uses
+  <blockquote>, so it indents on both sides.
+Chrome 12 dev: Outputs <blockquote class="webkit-indent-blockquote"
+  style="margin: 0 0 0 40px; border: none; padding: 0px"> in both modes for
+  both LTR and RTL (which is broken for RTL, since it indents only on the
+  right).
+Opera 11.00: Outputs <blockquote>, so it indents on both sides.
+
+For repeated indentation, everyone except Opera that outputs <blockquote>s just
+puts them at the outermost possible location, which works well.  Opera puts
+them in the innermost position, which is broken, because it will even put them
+inside <p> (which will not round-trip through text/html serialization).
+
+Gecko in CSS mode messes up by adding margins even to things like <blockquote>
+that already have margins from CSS rules, instead of nesting a div, so it
+doesn't actually increase the indentation.  However, if an element has an
+explicit left margin (assuming LTR), it will increase the margin to 80px, so it
+works with WebKit's blockquotes.
+-->
+
+<!--
+<dd><strong>Action</strong>:
+
+<ol>
+  <li>Let <var title>start node</var>, <var title>start offset</var>, <var title>end node</var>,
+  and <var title>end offset</var> be the <span data-anolis-spec=domrange title=concept-range-start>start</span> and <span data-anolis-spec=domrange title=concept-range-end>end</span> <span data-anolis-spec=domrange title=concept-boundary-point-node>nodes</span>
+  and <span data-anolis-spec=domrange title=concept-boundary-point-offset>offsets</span> of the <span data-anolis-spec=domrange title=concept-range>range</span>.
+
+  <p class=XXX>Handle corner cases: endpoints are detached, documents, document
+  fragments, html/body, head or things in head . . .
+
+  <li>Repeat the following steps:
+
+  <ol>
+    <li>If <var title>start node</var> is a <code data-anolis-spec=domcore>Text</code> or <code data-anolis-spec=domcore>Comment</code> node or
+    <var title>start offset</var> is 0, set <var title>start offset</var> to the <span data-anolis-spec=domrange title=concept-indexof>index</span>
+    of <var title>start node</var> and then set <var title>start node</var> to its
+    <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
+
+    <li>Otherwise, if the <var title>start offset</var> is equal to the
+    <span data-anolis-spec=domrange title=concept-node-length>length</span> of <var title>start node</var>, set <var title>start offset</var> to one
+    plus the <span data-anolis-spec=domrange title=concept-indexof>index</span> of <var title>start node</var> and then set <var title>start
+    node</var> to its <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
+
+    <li>Otherwise, if the <span data-anolis-spec=domcore title=concept-tree-child>child</span> of <var title>start node</var> with <span data-anolis-spec=domrange title=concept-indexof>index</span>
+    <var title>start offset</var> minus one is a <code data-anolis-spec=domcore>Text</code> or <code data-anolis-spec=domcore>Comment</code> node, or an
+    (insert definition here), subtract one from <var title>start offset</var>.
+
+    <p class=XXX>The definition should include all inline elements except
+    &lt;br>.  As elsewhere, we have trouble with the exact definition because
+    HTML doesn't classify non-conforming elements, but those are common in
+    editing and need to be handled correctly.
+
+    <li>Otherwise, break from this loop.
+  </ol>
+
+  <li>Repeat the following steps:
+
+  <ol>
+    <li>If <var title>end node</var> is a <code data-anolis-spec=domcore>Text</code> or <code data-anolis-spec=domcore>Comment</code> node or <var title>end
+    offset</var> is 0, set <var title>end offset</var> to the <span data-anolis-spec=domrange title=concept-indexof>index</span> of <var title>end
+    node</var> and then set <var title>end node</var> to its <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
+
+    <li>Otherwise, if the <var title>end offset</var> is equal to the <span data-anolis-spec=domrange title=concept-node-length>length</span>
+    of <var title>end node</var>, set <var title>end offset</var> to one plus the <span data-anolis-spec=domrange title=concept-indexof>index</span>
+    of <var title>end node</var> and then set <var title>end node</var> to its <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
+
+    <li>Otherwise, if the <span data-anolis-spec=domcore title=concept-tree-child>child</span> of <var title>end node</var> with <span data-anolis-spec=domrange title=concept-indexof>index</span>
+    <var title>end offset</var> plus one is a <code data-anolis-spec=domcore>Text</code> or <code data-anolis-spec=domcore>Comment</code> node, or an
+    (insert definition here), add one to <var title>end offset</var>.
+
+    <p class=XXX>Same definition as before.
+
+    <li>Otherwise, break from this loop.
+  </ol>
+</ol>
+
+<dd><strong>State</strong>:
+
+<dd><strong>Value</strong>:
+-->
+
+
 <dt><code title=""><dfn id=command-inserthorizontalrule title=command-inserthorizontalrule>insertHorizontalRule</dfn></code>
 
 <dd><strong>Action</strong>:
@@ -1953,6 +2044,15 @@
 
 
 <dt><code title=""><dfn id=command-stylewithcss title=command-stylewithcss>styleWithCSS</dfn></code>
+<!-- IE9 and Opera 11.00 don't support this command.  By and large, they act
+the way Gecko and WebKit do when styleWithCSS is off.  Gecko invented it, and
+WebKit also supports it:
+
+https://bugs.webkit.org/show_bug.cgi?id=13490
+
+The default in Firefox 4.0 is off, while all other browsers behave like the
+default is on (and IE/Opera give no way to turn it off), so I default it to on.
+-->
 
 <dd><strong>Action</strong>: Convert <var title="">value</var> to a boolean according
 to the algorithm in WebIDL, and set the <a href=#css-styling-flag>CSS styling flag</a> to the
@@ -2080,6 +2180,10 @@
 
 
 <dt><code title=""><dfn id=command-usecss title=command-usecss>useCSS</dfn></code>
+<!-- Supported by Firefox 4.0, but not IE9 or Opera 11.00 (which don't support
+styleWithCSS either), nor by Chrome 12 dev (which does support styleWithCSS.
+useCSS was the original feature in Mozilla 1.3, but the meaning is backward, so
+Gecko added styleWithCSS as a replacement. -->
 
 <dd><strong>Action</strong>: Convert <var title="">value</var> to a boolean according
 to the algorithm in WebIDL, and set the <a href=#css-styling-flag>CSS styling flag</a> to the
@@ -2089,6 +2193,10 @@
 
 <p class=XXX>Properly cross-reference.
 
+<p class=XXX>The meaning of this command is backwards, and only Gecko supports
+it.  It would be great if Gecko would agree to drop support, so that we could
+get rid of it.
+
 <dd><strong>State</strong>:
 
 <dd><strong>Value</strong>:
--- a/source.html	Wed Apr 13 14:01:04 2011 -0600
+++ b/source.html	Thu Apr 14 10:11:40 2011 -0600
@@ -1697,6 +1697,97 @@
 <dd><strong>Relevant CSS Property</strong>: "background-color"
 
 
+<!--
+<dt><code title><dfn title=command-indent>indent</dfn></code>
+-->
+<!--
+IE9: Outputs <blockquote style="margin-right: 0px" dir="ltr">, or when
+  surrounding RTL blocks, <blockquote style="margin-left: 0px" dir="rtl">.
+Firefox 4.0: In styleWithCSS mode, adds style="margin-left: 40px" to the
+  appropriate block container (or margin-right if it's RTL).  If there's no
+  appropriate block container, adds a div.  In non-styleWithCSS mode, uses
+  <blockquote>, so it indents on both sides.
+Chrome 12 dev: Outputs <blockquote class="webkit-indent-blockquote"
+  style="margin: 0 0 0 40px; border: none; padding: 0px"> in both modes for
+  both LTR and RTL (which is broken for RTL, since it indents only on the
+  right).
+Opera 11.00: Outputs <blockquote>, so it indents on both sides.
+
+For repeated indentation, everyone except Opera that outputs <blockquote>s just
+puts them at the outermost possible location, which works well.  Opera puts
+them in the innermost position, which is broken, because it will even put them
+inside <p> (which will not round-trip through text/html serialization).
+
+Gecko in CSS mode messes up by adding margins even to things like <blockquote>
+that already have margins from CSS rules, instead of nesting a div, so it
+doesn't actually increase the indentation.  However, if an element has an
+explicit left margin (assuming LTR), it will increase the margin to 80px, so it
+works with WebKit's blockquotes.
+-->
+
+<!--
+<dd><strong>Action</strong>:
+
+<ol>
+  <li>Let <var>start node</var>, <var>start offset</var>, <var>end node</var>,
+  and <var>end offset</var> be the [[rangestart]] and [[rangeend]] [[bpnodes]]
+  and [[bpoffsets]] of the [[range]].
+
+  <p class=XXX>Handle corner cases: endpoints are detached, documents, document
+  fragments, html/body, head or things in head . . .
+
+  <li>Repeat the following steps:
+
+  <ol>
+    <li>If <var>start node</var> is a [[text]] or [[comment]] node or
+    <var>start offset</var> is 0, set <var>start offset</var> to the [[index]]
+    of <var>start node</var> and then set <var>start node</var> to its
+    [[parent]].
+
+    <li>Otherwise, if the <var>start offset</var> is equal to the
+    [[nodelength]] of <var>start node</var>, set <var>start offset</var> to one
+    plus the [[index]] of <var>start node</var> and then set <var>start
+    node</var> to its [[parent]].
+
+    <li>Otherwise, if the [[child]] of <var>start node</var> with [[index]]
+    <var>start offset</var> minus one is a [[text]] or [[comment]] node, or an
+    (insert definition here), subtract one from <var>start offset</var>.
+
+    <p class=XXX>The definition should include all inline elements except
+    &lt;br>.  As elsewhere, we have trouble with the exact definition because
+    HTML doesn't classify non-conforming elements, but those are common in
+    editing and need to be handled correctly.
+
+    <li>Otherwise, break from this loop.
+  </ol>
+
+  <li>Repeat the following steps:
+
+  <ol>
+    <li>If <var>end node</var> is a [[text]] or [[comment]] node or <var>end
+    offset</var> is 0, set <var>end offset</var> to the [[index]] of <var>end
+    node</var> and then set <var>end node</var> to its [[parent]].
+
+    <li>Otherwise, if the <var>end offset</var> is equal to the [[nodelength]]
+    of <var>end node</var>, set <var>end offset</var> to one plus the [[index]]
+    of <var>end node</var> and then set <var>end node</var> to its [[parent]].
+
+    <li>Otherwise, if the [[child]] of <var>end node</var> with [[index]]
+    <var>end offset</var> plus one is a [[text]] or [[comment]] node, or an
+    (insert definition here), add one to <var>end offset</var>.
+
+    <p class=XXX>Same definition as before.
+
+    <li>Otherwise, break from this loop.
+  </ol>
+</ol>
+
+<dd><strong>State</strong>:
+
+<dd><strong>Value</strong>:
+-->
+
+
 <dt><code title><dfn title=command-inserthorizontalrule>insertHorizontalRule</dfn></code>
 
 <dd><strong>Action</strong>:
@@ -1988,6 +2079,15 @@
 
 
 <dt><code title><dfn title=command-stylewithcss>styleWithCSS</dfn></code>
+<!-- IE9 and Opera 11.00 don't support this command.  By and large, they act
+the way Gecko and WebKit do when styleWithCSS is off.  Gecko invented it, and
+WebKit also supports it:
+
+https://bugs.webkit.org/show_bug.cgi?id=13490
+
+The default in Firefox 4.0 is off, while all other browsers behave like the
+default is on (and IE/Opera give no way to turn it off), so I default it to on.
+-->
 
 <dd><strong>Action</strong>: Convert <var>value</var> to a boolean according
 to the algorithm in WebIDL, and set the <span>CSS styling flag</span> to the
@@ -2117,6 +2217,10 @@
 
 
 <dt><code title><dfn title=command-usecss>useCSS</dfn></code>
+<!-- Supported by Firefox 4.0, but not IE9 or Opera 11.00 (which don't support
+styleWithCSS either), nor by Chrome 12 dev (which does support styleWithCSS.
+useCSS was the original feature in Mozilla 1.3, but the meaning is backward, so
+Gecko added styleWithCSS as a replacement. -->
 
 <dd><strong>Action</strong>: Convert <var>value</var> to a boolean according
 to the algorithm in WebIDL, and set the <span>CSS styling flag</span> to the
@@ -2128,6 +2232,10 @@
 
 <p class=XXX>Properly cross-reference.
 
+<p class=XXX>The meaning of this command is backwards, and only Gecko supports
+it.  It would be great if Gecko would agree to drop support, so that we could
+get rid of it.
+
 <dd><strong>State</strong>:
 
 <dd><strong>Value</strong>: