Spec and implement insertHTML
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Tue, 31 May 2011 15:40:27 -0600
changeset 220 a40600dec9a9
parent 219 4cbff22dbf0f
child 221 58269f1b58b5
Spec and implement insertHTML
autoimplementation.html
editcommands.html
implementation.js
preprocess
source.html
--- a/autoimplementation.html	Tue May 31 14:53:34 2011 -0600
+++ b/autoimplementation.html	Tue May 31 15:40:27 2011 -0600
@@ -903,6 +903,15 @@
 		'<h1>foo[bar]baz</h1>',
 		'<p>foo<b>b[a]r</b>baz</p>',
 	],
+	inserthtml: [
+		'foo[]bar',
+		'foo[bar]baz',
+		['<b>', 'foo[bar]baz'],
+		['<b>abc', 'foo[bar]baz'],
+		['<p>abc', '<p>foo[bar]baz'],
+		['<li>abc', '<p>foo[bar]baz'],
+		['<p>abc', '<ol>{<li>foo</li>}<li>bar</ol>'],
+	],
 	insertimage: [
 		'foo[]bar',
 		'<span>foo</span>{}<span>bar</span>',
@@ -1938,6 +1947,7 @@
 	formatblock: "<div>",
 	hilitecolor: "#FF8888",
 	inserthorizontalrule: "",
+	inserthtml: "ab<b>c</b>d",
 	insertimage: "/img/lion.svg",
 };
 
--- a/editcommands.html	Tue May 31 14:53:34 2011 -0600
+++ b/editcommands.html	Tue May 31 15:40:27 2011 -0600
@@ -87,14 +87,15 @@
    <li><a href=#the-fontsize-command><span class=secno>6.12 </span>The <code title="">fontSize</code> command</a></li>
    <li><a href=#the-forecolor-command><span class=secno>6.13 </span>The <code title="">foreColor</code> command</a></li>
    <li><a href=#the-hilitecolor-command><span class=secno>6.14 </span>The <code title="">hiliteColor</code> command</a></li>
-   <li><a href=#the-insertimage-command><span class=secno>6.15 </span>The <code title="">insertImage</code> command</a></li>
-   <li><a href=#the-italic-command><span class=secno>6.16 </span>The <code title="">italic</code> command</a></li>
-   <li><a href=#the-removeformat-command><span class=secno>6.17 </span>The <code title="">removeFormat</code> command</a></li>
-   <li><a href=#the-strikethrough-command><span class=secno>6.18 </span>The <code title="">strikethrough</code> command</a></li>
-   <li><a href=#the-subscript-command><span class=secno>6.19 </span>The <code title="">subscript</code> command</a></li>
-   <li><a href=#the-superscript-command><span class=secno>6.20 </span>The <code title="">superscript</code> command</a></li>
-   <li><a href=#the-underline-command><span class=secno>6.21 </span>The <code title="">underline</code> command</a></li>
-   <li><a href=#the-unlink-command><span class=secno>6.22 </span>The <code title="">unlink</code> command</a></ol></li>
+   <li><a href=#the-inserthtml-command><span class=secno>6.15 </span>The <code title="">insertHTML</code> command</a></li>
+   <li><a href=#the-insertimage-command><span class=secno>6.16 </span>The <code title="">insertImage</code> command</a></li>
+   <li><a href=#the-italic-command><span class=secno>6.17 </span>The <code title="">italic</code> command</a></li>
+   <li><a href=#the-removeformat-command><span class=secno>6.18 </span>The <code title="">removeFormat</code> command</a></li>
+   <li><a href=#the-strikethrough-command><span class=secno>6.19 </span>The <code title="">strikethrough</code> command</a></li>
+   <li><a href=#the-subscript-command><span class=secno>6.20 </span>The <code title="">subscript</code> command</a></li>
+   <li><a href=#the-superscript-command><span class=secno>6.21 </span>The <code title="">superscript</code> command</a></li>
+   <li><a href=#the-underline-command><span class=secno>6.22 </span>The <code title="">underline</code> command</a></li>
+   <li><a href=#the-unlink-command><span class=secno>6.23 </span>The <code title="">unlink</code> command</a></ol></li>
  <li><a href=#block-formatting-commands><span class=secno>7 </span>Block formatting commands</a>
   <ol>
    <li><a href=#block-formatting-command-definitions><span class=secno>7.1 </span>Block formatting command definitions</a></li>
@@ -377,15 +378,17 @@
 editing host itself.
 
 <p>A <dfn id=prohibited-paragraph-child-name>prohibited paragraph child name</dfn> is "address", "article",
-"aside", "blockquote", "center", "details", "dd", "dir", "div", "dl", "dt",
-"fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4",
-"h5", "h6", "header", "hgroup", "hr", "li", "listing", "menu", "nav", "ol",
-"p", "plaintext", "pre", "section", "summary", "table", "ul", or "xmp".
+"aside", "blockquote", "caption", "center", "col", "colgroup", "details", "dd",
+"dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form",
+"h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li", "listing",
+"menu", "nav", "ol", "p", "plaintext", "pre", "section", "summary", "table",
+"tbody", "td", "tfoot", "th", "thead", "tr", "ul", or "xmp".
 
 <p>A <dfn id=prohibited-paragraph-child>prohibited paragraph child</dfn> is an <a href=#html-element>HTML element</a>
 whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-element-local-name title=concept-element-local-name>local name</a> is a <a href=#prohibited-paragraph-child-name>prohibited paragraph child name</a>.
 <!-- These are all the things that will close a <p> if found as a descendant.
-I think. -->
+I think.  Plus table stuff, since that can't be a descendant of a p either,
+although it won't auto-close it. -->
 
 <p>A <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> or string <var title="">child</var> is an <dfn id=allowed-child>allowed child</dfn> of a
 <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> or string <var title="">parent</var> if the following algorithm returns true:
@@ -1111,10 +1114,6 @@
 * justifyCenter, justifyFull, justifyLeft, justifyRight: These look important
   and should be similar to work I've already done, so they're
   next on my list.
-* insertHTML: Not supported by IE, but important.  I've seen frameworks that
-  work around its absence in IE by using insertImage and then search and
-  replace.  Will probably need to be defined in terms of insertAdjacentHTML or
-  something.
 * selectAll, unselect: Should be easy, although they seem redundant to just
   calling methods of the Selection.
 -->
@@ -2487,7 +2486,35 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "background-color"
 
 
-<h3 id=the-insertimage-command><span class=secno>6.15 </span><dfn>The <code title="">insertImage</code> command</dfn></h3>
+<h3 id=the-inserthtml-command><span class=secno>6.15 </span><dfn>The <code title="">insertHTML</code> command</dfn></h3>
+<!-- Not supported by IE9. -->
+
+<p><a href=#action>Action</a>:
+
+<ol>
+  <li><a href=#delete-the-selection>Delete the selection</a>.
+
+  <li>Let <var title="">frag</var> be the result of calling <code class=external data-anolis-spec=domps title=dom-Range-createContextualFragment><a href=http://html5.org/specs/dom-parsing.html#dom-range-createcontextualfragment>createContextualFragment(<var title="">value</var>)</a></code>
+  on the <a href=#active-range>active range</a>.
+
+  <li>Let <var title="">descendants</var> be all <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-descendant title=concept-tree-descendant>descendants</a> of <var title="">frag</var>.
+
+  <li>Let <var title="">last child</var> be the <code class=external data-anolis-spec=domcore title=dom-Node-lastChild><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-lastchild>lastChild</a></code> of <var title="">frag</var>.
+
+  <li>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="">frag</var>)</a></code> on the <a href=#active-range>active range</a>.
+
+  <li>If <var title="">last child</var> is not null, set the <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
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> of the <a href=#active-range>active range</a> to (<var title="">last child</var>,
+  <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="">last child</var>).
+  <!-- Need to do this before fixing disallowed ancestors, since otherwise the
+  last child might have been removed (e.g., it's an li). -->
+
+  <li><a href=#fix-disallowed-ancestors>Fix disallowed ancestors</a> of each member of
+  <var title="">descendants</var>.
+</ol>
+
+
+<h3 id=the-insertimage-command><span class=secno>6.16 </span><dfn>The <code title="">insertImage</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -2523,7 +2550,7 @@
 </ol>
 
 
-<h3 id=the-italic-command><span class=secno>6.16 </span><dfn>The <code title="">italic</code> command</dfn></h3>
+<h3 id=the-italic-command><span class=secno>6.17 </span><dfn>The <code title="">italic</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>.
 If the <a href=#state>state</a> is then false, <a href=#set-the-value>set the value</a> of each
@@ -2539,7 +2566,7 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "font-style"
 
 
-<h3 id=the-removeformat-command><span class=secno>6.17 </span><dfn>The <code title="">removeFormat</code> command</dfn></h3>
+<h3 id=the-removeformat-command><span class=secno>6.18 </span><dfn>The <code title="">removeFormat</code> command</dfn></h3>
 <!--
 Tested in IE 9, Firefox 4.0, Chrome 12 dev, Opera 11.00.
 
@@ -2660,7 +2687,7 @@
 </ol>
 
 
-<h3 id=the-strikethrough-command><span class=secno>6.18 </span><dfn>The <code title="">strikethrough</code> command</dfn></h3>
+<h3 id=the-strikethrough-command><span class=secno>6.19 </span><dfn>The <code title="">strikethrough</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>.
 If the <a href=#state>state</a> is then false, <a href=#set-the-value>set the value</a> of each
@@ -2676,7 +2703,7 @@
 <p><a href=#value>Value</a>: Always the empty string.
 
 
-<h3 id=the-subscript-command><span class=secno>6.19 </span><dfn>The <code title="">subscript</code> command</dfn></h3>
+<h3 id=the-subscript-command><span class=secno>6.20 </span><dfn>The <code title="">subscript</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -2701,7 +2728,7 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "vertical-align"
 
 
-<h3 id=the-superscript-command><span class=secno>6.20 </span><dfn>The <code title="">superscript</code> command</dfn></h3>
+<h3 id=the-superscript-command><span class=secno>6.21 </span><dfn>The <code title="">superscript</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -2726,7 +2753,7 @@
 <p><a href=#relevant-css-property>Relevant CSS property</a>: "vertical-align"
 
 
-<h3 id=the-underline-command><span class=secno>6.21 </span><dfn>The <code title="">underline</code> command</dfn></h3>
+<h3 id=the-underline-command><span class=secno>6.22 </span><dfn>The <code title="">underline</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>: <a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>.
 If the <a href=#state>state</a> is then false, <a href=#set-the-value>set the value</a> of each
@@ -2788,7 +2815,7 @@
 <p><a href=#value>Value</a>: Always the empty string.
 
 
-<h3 id=the-unlink-command><span class=secno>6.22 </span><dfn>The <code title="">unlink</code> command</dfn></h3>
+<h3 id=the-unlink-command><span class=secno>6.23 </span><dfn>The <code title="">unlink</code> command</dfn></h3>
 
 <p><a href=#action>Action</a>:
 
@@ -2869,6 +2896,10 @@
     <li><a href=#set-the-tag-name>Set the tag name</a> of <var title="">node</var> to the <a href=#default-single-line-container-name>default
     single-line container name</a>, and let <var title="">node</var> be the result.
 
+    <li><a href=#fix-disallowed-ancestors>Fix disallowed ancestors</a> of <var title="">node</var>.
+    <!-- Because maybe it somehow wound up as the child of a p, like via
+    insertHTML. -->
+
     <li><a href=#fix-prohibited-paragraph-descendants>Fix prohibited paragraph descendants</a> of <var title="">node</var>.
 
     <li>Abort these steps.
--- a/implementation.js	Tue May 31 14:53:34 2011 -0600
+++ b/implementation.js	Tue May 31 15:40:27 2011 -0600
@@ -72,6 +72,15 @@
 	return Boolean(node1.compareDocumentPosition(node2) & Node.DOCUMENT_POSITION_PRECEDING);
 }
 
+function getDescendants(node) {
+	var descendants = [];
+	for (var i = 0; i < node.childNodes.length; i++) {
+		descendants.push(node.childNodes[i]);
+		descendants = descendants.concat(getDescendants(node.childNodes[i]));
+	}
+	return descendants;
+}
+
 function convertProperty(property) {
 	// Special-case for now
 	var map = {
@@ -973,16 +982,19 @@
 }
 
 // "A prohibited paragraph child name is "address", "article", "aside",
-// "blockquote", "center", "details", "dd", "dir", "div", "dl", "dt",
-// "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3",
-// "h4", "h5", "h6", "header", "hgroup", "hr", "li", "listing", "menu", "nav",
-// "ol", "p", "plaintext", "pre", "section", "summary", "table", "ul", or
+// "blockquote", "caption", "center", "col", "colgroup", "details", "dd",
+// "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
+// "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
+// "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
+// "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or
 // "xmp"."
 var prohibitedParagraphChildNames = ["address", "article", "aside",
-	"blockquote", "center", "details", "dd", "dir", "div", "dl", "dt",
-	"fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3",
-	"h4", "h5", "h6", "header", "hgroup", "hr", "li", "listing", "menu", "nav",
-	"ol", "p", "plaintext", "pre", "section", "summary", "table", "ul", "xmp"];
+	"blockquote", "caption", "center", "col", "colgroup", "details", "dd",
+	"dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
+	"form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
+	"listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
+	"summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul",
+	"xmp"];
 
 // "A prohibited paragraph child is an HTML element whose local name is a
 // prohibited paragraph child name."
@@ -3131,6 +3143,36 @@
 		getSelection().addRange(range);
 		break;
 
+		case "inserthtml":
+		// "Delete the selection."
+		deleteSelection();
+
+		// "Let frag be the result of calling createContextualFragment(value)
+		// on the active range."
+		var frag = range.createContextualFragment(value);
+
+		// "Let descendants be all descendants of frag."
+		var descendants = getDescendants(frag);
+
+		// "Let last child be the lastChild of frag."
+		var lastChild = frag.lastChild;
+
+		// "Call insertNode(frag) on the active range."
+		range.insertNode(frag);
+
+		// "If last child is not null, set the start and end of the active
+		// range to (last child, length of last child)."
+		if (lastChild) {
+			range.setStart(lastChild, getNodeLength(lastChild));
+			range.setEnd(lastChild, getNodeLength(lastChild));
+		}
+
+		// "Fix disallowed ancestors of each member of descendants."
+		for (var i = 0; i < descendants.length; i++) {
+			fixDisallowedAncestors(descendants[i]);
+		}
+		break;
+
 		case "insertimage":
 		// "If value is the empty string, abort these steps and do nothing."
 		if (value === "") {
@@ -3724,6 +3766,9 @@
 		// and let node be the result."
 		node = setTagName(node, defaultSingleLineContainerName);
 
+		// "Fix disallowed ancestors of node."
+		fixDisallowedAncestors(node);
+
 		// "Fix prohibited paragraph descendants of node."
 		fixProhibitedParagraphDescendants(node);
 
--- a/preprocess	Tue May 31 14:53:34 2011 -0600
+++ b/preprocess	Tue May 31 15:40:27 2011 -0600
@@ -39,6 +39,7 @@
     'em': '<code data-anolis-spec=html title="the em element">em</code>',
     'endnode': '<span data-anolis-spec=domrange title=concept-range-end>end</span> <span data-anolis-spec=domrange title=concept-boundary-point-node>node</span>',
     'endoffset': '<span data-anolis-spec=domrange title=concept-range-end>end</span> <span data-anolis-spec=domrange title=concept-boundary-point-offset>offset</span>',
+    'firstchild': '<code data-anolis-spec=domcore title=dom-Node-firstChild>firstChild</code>',
     'followingsibling': '<span data-anolis-spec=domcore title="concept-tree-following-sibling">following sibling</span>',
     'font': '<code data-anolis-spec=html title=font>font</code>',
     'fontcolor': '<code data-anolis-spec=html title=dom-font-color>color</code>',
@@ -50,6 +51,7 @@
     'href': '<code data-anolis-spec=html title=attr-hyperlink-href>href</code>',
     'i': '<code data-anolis-spec=html title="the i element">i</code>',
     'index': '<span data-anolis-spec=domrange title=concept-indexof>index</span>',
+    'lastchild': '<code data-anolis-spec=domcore title=dom-Node-lastChild>lastChild</code>',
     'li': '<code data-anolis-spec=html title="the li element">li</code>',
     'localname': '<span data-anolis-spec=domcore title=concept-element-local-name>local name</span>',
     'namespace': '<span data-anolis-spec=domcore title=concept-element-namespace>namespace</span>',
--- a/source.html	Tue May 31 14:53:34 2011 -0600
+++ b/source.html	Tue May 31 15:40:27 2011 -0600
@@ -333,15 +333,17 @@
 editing host itself.
 
 <p>A <dfn>prohibited paragraph child name</dfn> is "address", "article",
-"aside", "blockquote", "center", "details", "dd", "dir", "div", "dl", "dt",
-"fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4",
-"h5", "h6", "header", "hgroup", "hr", "li", "listing", "menu", "nav", "ol",
-"p", "plaintext", "pre", "section", "summary", "table", "ul", or "xmp".
+"aside", "blockquote", "caption", "center", "col", "colgroup", "details", "dd",
+"dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form",
+"h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li", "listing",
+"menu", "nav", "ol", "p", "plaintext", "pre", "section", "summary", "table",
+"tbody", "td", "tfoot", "th", "thead", "tr", "ul", or "xmp".
 
 <p>A <dfn>prohibited paragraph child</dfn> is an <span>HTML element</span>
 whose [[localname]] is a <span>prohibited paragraph child name</span>.
 <!-- These are all the things that will close a <p> if found as a descendant.
-I think. -->
+I think.  Plus table stuff, since that can't be a descendant of a p either,
+although it won't auto-close it. -->
 
 <p>A [[node]] or string <var>child</var> is an <dfn>allowed child</dfn> of a
 [[node]] or string <var>parent</var> if the following algorithm returns true:
@@ -1082,10 +1084,6 @@
 * justifyCenter, justifyFull, justifyLeft, justifyRight: These look important
   and should be similar to work I've already done, so they're
   next on my list.
-* insertHTML: Not supported by IE, but important.  I've seen frameworks that
-  work around its absence in IE by using insertImage and then search and
-  replace.  Will probably need to be defined in terms of insertAdjacentHTML or
-  something.
 * selectAll, unselect: Should be easy, although they seem redundant to just
   calling methods of the Selection.
 -->
@@ -2481,6 +2479,35 @@
 <p><span>Relevant CSS property</span>: "background-color"
 
 
+<h3><dfn>The <code title>insertHTML</code> command</dfn></h3>
+<!-- Not supported by IE9. -->
+
+<p><span>Action</span>:
+
+<ol>
+  <li><span>Delete the selection</span>.
+
+  <li>Let <var>frag</var> be the result of calling <code data-anolis-spec=domps
+  title=dom-Range-createContextualFragment>createContextualFragment(<var>value</var>)</code>
+  on the <span>active range</span>.
+
+  <li>Let <var>descendants</var> be all [[descendants]] of <var>frag</var>.
+
+  <li>Let <var>last child</var> be the [[lastchild]] of <var>frag</var>.
+
+  <li>Call [[insertnode|<var>frag</var>]] on the <span>active range</span>.
+
+  <li>If <var>last child</var> is not null, set the [[rangestart]] and
+  [[rangeend]] of the <span>active range</span> to (<var>last child</var>,
+  [[nodelength]] of <var>last child</var>).
+  <!-- Need to do this before fixing disallowed ancestors, since otherwise the
+  last child might have been removed (e.g., it's an li). -->
+
+  <li><span>Fix disallowed ancestors</span> of each member of
+  <var>descendants</var>.
+</ol>
+
+
 <h3><dfn>The <code title>insertImage</code> command</dfn></h3>
 
 <p><span>Action</span>:
@@ -2871,6 +2898,10 @@
     <li><span>Set the tag name</span> of <var>node</var> to the <span>default
     single-line container name</span>, and let <var>node</var> be the result.
 
+    <li><span>Fix disallowed ancestors</span> of <var>node</var>.
+    <!-- Because maybe it somehow wound up as the child of a p, like via
+    insertHTML. -->
+
     <li><span>Fix prohibited paragraph descendants</span> of <var>node</var>.
 
     <li>Abort these steps.