--- a/editcommands.html Tue Jun 14 13:46:20 2011 -0600
+++ b/editcommands.html Tue Jun 14 14:37:27 2011 -0600
@@ -87,20 +87,19 @@
<li><a href=#the-backcolor-command><span class=secno>6.8 </span>The <code title="">backColor</code> command</a></li>
<li><a href=#the-bold-command><span class=secno>6.9 </span>The <code title="">bold</code> command</a></li>
<li><a href=#the-createlink-command><span class=secno>6.10 </span>The <code title="">createLink</code> command</a></li>
- <li><a href=#the-delete-command><span class=secno>6.11 </span>The <code title="">delete</code> command</a></li>
- <li><a href=#the-fontname-command><span class=secno>6.12 </span>The <code title="">fontName</code> command</a></li>
- <li><a href=#the-fontsize-command><span class=secno>6.13 </span>The <code title="">fontSize</code> command</a></li>
- <li><a href=#the-forecolor-command><span class=secno>6.14 </span>The <code title="">foreColor</code> command</a></li>
- <li><a href=#the-hilitecolor-command><span class=secno>6.15 </span>The <code title="">hiliteColor</code> command</a></li>
- <li><a href=#the-inserthtml-command><span class=secno>6.16 </span>The <code title="">insertHTML</code> command</a></li>
- <li><a href=#the-insertimage-command><span class=secno>6.17 </span>The <code title="">insertImage</code> command</a></li>
- <li><a href=#the-italic-command><span class=secno>6.18 </span>The <code title="">italic</code> command</a></li>
- <li><a href=#the-removeformat-command><span class=secno>6.19 </span>The <code title="">removeFormat</code> command</a></li>
- <li><a href=#the-strikethrough-command><span class=secno>6.20 </span>The <code title="">strikethrough</code> command</a></li>
- <li><a href=#the-subscript-command><span class=secno>6.21 </span>The <code title="">subscript</code> command</a></li>
- <li><a href=#the-superscript-command><span class=secno>6.22 </span>The <code title="">superscript</code> command</a></li>
- <li><a href=#the-underline-command><span class=secno>6.23 </span>The <code title="">underline</code> command</a></li>
- <li><a href=#the-unlink-command><span class=secno>6.24 </span>The <code title="">unlink</code> command</a></ol></li>
+ <li><a href=#the-fontname-command><span class=secno>6.11 </span>The <code title="">fontName</code> command</a></li>
+ <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-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>
@@ -110,17 +109,18 @@
<li><a href=#outdenting-a-node><span class=secno>7.5 </span>Outdenting a node</a></li>
<li><a href=#toggling-lists><span class=secno>7.6 </span>Toggling lists</a></li>
<li><a href=#justifying-the-selection><span class=secno>7.7 </span>Justifying the selection</a></li>
- <li><a href=#the-formatblock-command><span class=secno>7.8 </span>The <code title="">formatBlock</code> command</a></li>
- <li><a href=#the-indent-command><span class=secno>7.9 </span>The <code title="">indent</code> command</a></li>
- <li><a href=#the-inserthorizontalrule-command><span class=secno>7.10 </span>The <code title="">insertHorizontalRule</code> command</a></li>
- <li><a href=#the-insertorderedlist-command><span class=secno>7.11 </span>The <code title="">insertOrderedList</code> command</a></li>
- <li><a href=#the-insertparagraph-command><span class=secno>7.12 </span>The <code title="">insertParagraph</code> command</a></li>
- <li><a href=#the-insertunorderedlist-command><span class=secno>7.13 </span>The <code title="">insertUnorderedList</code> command</a></li>
- <li><a href=#the-justifycenter-command><span class=secno>7.14 </span>The <code title="">justifyCenter</code> command</a></li>
- <li><a href=#the-justifyfull-command><span class=secno>7.15 </span>The <code title="">justifyFull</code> command</a></li>
- <li><a href=#the-justifyleft-command><span class=secno>7.16 </span>The <code title="">justifyLeft</code> command</a></li>
- <li><a href=#the-justifyright-command><span class=secno>7.17 </span>The <code title="">justifyRight</code> command</a></li>
- <li><a href=#the-outdent-command><span class=secno>7.18 </span>The <code title="">outdent</code> command</a></ol></li>
+ <li><a href=#the-delete-command><span class=secno>7.8 </span>The <code title="">delete</code> command</a></li>
+ <li><a href=#the-formatblock-command><span class=secno>7.9 </span>The <code title="">formatBlock</code> command</a></li>
+ <li><a href=#the-indent-command><span class=secno>7.10 </span>The <code title="">indent</code> command</a></li>
+ <li><a href=#the-inserthorizontalrule-command><span class=secno>7.11 </span>The <code title="">insertHorizontalRule</code> command</a></li>
+ <li><a href=#the-insertorderedlist-command><span class=secno>7.12 </span>The <code title="">insertOrderedList</code> command</a></li>
+ <li><a href=#the-insertparagraph-command><span class=secno>7.13 </span>The <code title="">insertParagraph</code> command</a></li>
+ <li><a href=#the-insertunorderedlist-command><span class=secno>7.14 </span>The <code title="">insertUnorderedList</code> command</a></li>
+ <li><a href=#the-justifycenter-command><span class=secno>7.15 </span>The <code title="">justifyCenter</code> command</a></li>
+ <li><a href=#the-justifyfull-command><span class=secno>7.16 </span>The <code title="">justifyFull</code> command</a></li>
+ <li><a href=#the-justifyleft-command><span class=secno>7.17 </span>The <code title="">justifyLeft</code> command</a></li>
+ <li><a href=#the-justifyright-command><span class=secno>7.18 </span>The <code title="">justifyRight</code> command</a></li>
+ <li><a href=#the-outdent-command><span class=secno>7.19 </span>The <code title="">outdent</code> command</a></ol></li>
<li><a href=#miscellaneous-commands><span class=secno>8 </span>Miscellaneous commands</a>
<ol>
<li><a href=#the-selectall-command><span class=secno>8.1 </span>The <code title="">selectAll</code> command</a></li>
@@ -411,6 +411,79 @@
things. If it is, it needs some adjustment, like to handle collapsed
whitespace nodes and collapsed br's.
+<!-- I don't remember why I wrote this. Keeping it around just in case it
+turns out to be useful.
+
+<p><var title>node</var> is an <dfn>extraneous line break</dfn> if the following
+algorithm returns true:
+
+<p class=XXX>This is positively horrible. It attempts to move complicated CSS
+logic into DOM methods, and does so badly. Can we somehow make this less evil,
+preferably much less evil?
+
+<ol>
+ <li>If <var title>node</var> is not a <code data-anolis-spec=html title="the br element">br</code>, return false.
+
+ <li>Let <var title>ancestor block</var> be <var title>node</var>.
+
+ <li>While <var title>ancestor block</var> is an <span>inline node</span>, set
+ <var title>ancestor block</var> to its <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
+
+ <li>Let <var title>previous box</var> be <var title>node</var>.
+
+ <li>While <var title>previous box</var> is equal to or an <span data-anolis-spec=domcore title=concept-tree-ancestor>ancestor</span> of
+ <var title>node</var>, set <var title>previous box</var> to the <span data-anolis-spec=domcore title=concept-node>node</span> immediately
+ before it in <span data-anolis-spec=domcore>tree order</span>, or null if there is no such <span data-anolis-spec=domcore title=concept-node>node</span>.
+
+ <li>Let <var title>next box</var> be the <span data-anolis-spec=domcore title=concept-node>node</span> immediately after <var title>node</var>
+ in <span data-anolis-spec=domcore>tree order</span>, or null if there is such <span data-anolis-spec=domcore title=concept-node>node</span>.
+
+ <li>If <var title>previous box</var> is null, or is not a <span data-anolis-spec=domcore title=concept-tree-descendant>descendant</span> of
+ <var title>ancestor block</var>, or is not an <span>inline node</span>, or is a
+ <code data-anolis-spec=html title="the br element">br</code>, return false.
+ <!- -
+ This means br either is the first thing in its block container that generates
+ an inline box, or it's the first thing after another block container, or the
+ first thing after a br. In any case, the line break will be visible.
+
+ Otherwise, it will be invisible as long as it immediately precedes a block
+ box boundary.
+ - ->
+
+ <li>If <var title>ancestor block</var> is not null and <var title>node</var> is the last
+ <span data-anolis-spec=domcore title=concept-tree-descendant>descendant</span> of <var title>ancestor block</var>, return true.
+ <!- - Precedes the end of ancestor block's box. - ->
+
+ <li>If <var title>next box</var> is not null and not an <span>inline node</span>,
+ return true.
+ <!- - Precedes the start of next box's box. - ->
+
+ <li>Return false.
+</ol>
+-->
+
+<p>Each <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> has a boolean <dfn id=css-styling-flag>CSS styling flag</dfn> associated
+with it, which must initially be false. (<a href=#the-stylewithcss-command>The <code title="">styleWithCSS</code> command</a> can be used to modify or query it, by
+means of the <code><a href=#execcommand()>execCommand()</a></code> and <code><a href=#querycommandstate()>queryCommandState()</a></code>
+methods.)
+
+<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,
+if the author has overridden the method with a custom method, the standard
+method must be run rather than the custom one.
+
+<p>When a list or set 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> is assigned to a variable without specifying
+the order, they must be initially in <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>, if they share a root.
+(If they don't share a root, the order will be specified.) When the user agent
+is instructed to run particular steps for each member of a list, it must do so
+sequentially in the list's order.
+
+
+<h2 id=common-algorithms><span class=secno>5 </span>Common algorithms</h2>
+
+<h3 id=assorted-common-algorithms><span class=secno>5.1 </span>Assorted common algorithms</h3>
+
<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:
@@ -534,78 +607,34 @@
<li>Return true.
</ol>
-<!-- I don't remember why I wrote this. Keeping it around just in case it
-turns out to be useful.
-
-<p><var title>node</var> is an <dfn>extraneous line break</dfn> if the following
-algorithm returns true:
-
-<p class=XXX>This is positively horrible. It attempts to move complicated CSS
-logic into DOM methods, and does so badly. Can we somehow make this less evil,
-preferably much less evil?
+<p>To move 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> to a new location, <dfn id=preserving-ranges>preserving ranges</dfn>, remove
+the <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> from its original <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> (if any), then insert it in the new
+location. In doing so, however, ignore the regular <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#range-mutation-rules>range mutation rules</a>, and
+instead follow these rules:
<ol>
- <li>If <var title>node</var> is not a <code data-anolis-spec=html title="the br element">br</code>, return false.
-
- <li>Let <var title>ancestor block</var> be <var title>node</var>.
-
- <li>While <var title>ancestor block</var> is an <span>inline node</span>, set
- <var title>ancestor block</var> to its <span data-anolis-spec=domcore title=concept-tree-parent>parent</span>.
-
- <li>Let <var title>previous box</var> be <var title>node</var>.
-
- <li>While <var title>previous box</var> is equal to or an <span data-anolis-spec=domcore title=concept-tree-ancestor>ancestor</span> of
- <var title>node</var>, set <var title>previous box</var> to the <span data-anolis-spec=domcore title=concept-node>node</span> immediately
- before it in <span data-anolis-spec=domcore>tree order</span>, or null if there is no such <span data-anolis-spec=domcore title=concept-node>node</span>.
-
- <li>Let <var title>next box</var> be the <span data-anolis-spec=domcore title=concept-node>node</span> immediately after <var title>node</var>
- in <span data-anolis-spec=domcore>tree order</span>, or null if there is such <span data-anolis-spec=domcore title=concept-node>node</span>.
-
- <li>If <var title>previous box</var> is null, or is not a <span data-anolis-spec=domcore title=concept-tree-descendant>descendant</span> of
- <var title>ancestor block</var>, or is not an <span>inline node</span>, or is a
- <code data-anolis-spec=html title="the br element">br</code>, return false.
- <!- -
- This means br either is the first thing in its block container that generates
- an inline box, or it's the first thing after another block container, or the
- first thing after a br. In any case, the line break will be visible.
-
- Otherwise, it will be invisible as long as it immediately precedes a block
- box boundary.
- - ->
-
- <li>If <var title>ancestor block</var> is not null and <var title>node</var> is the last
- <span data-anolis-spec=domcore title=concept-tree-descendant>descendant</span> of <var title>ancestor block</var>, return true.
- <!- - Precedes the end of ancestor block's box. - ->
-
- <li>If <var title>next box</var> is not null and not an <span>inline node</span>,
- return true.
- <!- - Precedes the start of next box's box. - ->
-
- <li>Return false.
+ <li>Let <var title="">node</var> be the moved <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>, <var title="">old parent</var> and
+ <var title="">old index</var> be the old <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> (which may be null) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>,
+ and <var title="">new parent</var> and <var title="">new index</var> be the new <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> and
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>.
+
+ <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is the same as or a <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>descendant</a> of
+ <var title="">node</var>, leave it unchanged, so it moves to the new location. <!--
+ This is actually implicit, but I state it anyway for completeness. -->
+
+ <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">new parent</var> and its
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is greater than <var title="">new index</var>, add one to its
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
+
+ <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">old parent</var> and its
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is <var title="">old index</var> or <var title="">old index</var> + 1, set its
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> to <var title="">new parent</var> and add <var title="">new index</var> −
+ <var title="">old index</var> to its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
+
+ <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">old parent</var> and its
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is greater than <var title="">old index</var> + 1, subtract one from its
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
</ol>
--->
-
-<p>Each <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> has a boolean <dfn id=css-styling-flag>CSS styling flag</dfn> associated
-with it, which must initially be false. (<a href=#the-stylewithcss-command>The <code title="">styleWithCSS</code> command</a> can be used to modify or query it, by
-means of the <code><a href=#execcommand()>execCommand()</a></code> and <code><a href=#querycommandstate()>queryCommandState()</a></code>
-methods.)
-
-<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,
-if the author has overridden the method with a custom method, the standard
-method must be run rather than the custom one.
-
-<p>When a list or set 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> is assigned to a variable without specifying
-the order, they must be initially in <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a>, if they share a root.
-(If they don't share a root, the order will be specified.) When the user agent
-is instructed to run particular steps for each member of a list, it must do so
-sequentially in the list's order.
-
-
-<h2 id=common-algorithms><span class=secno>5 </span>Common algorithms</h2>
-
-<h3 id=assorted-common-algorithms><span class=secno>5.1 </span>Assorted common algorithms</h3>
<p>To <dfn id=set-the-tag-name>set the tag name</dfn> of an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> <var title="">element</var> to
<var title="">new name</var>:
@@ -786,35 +815,6 @@
descendants</dfn>, <a href=#split-the-parent>split the parent</a> of <var title="">node</var>'s
<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>.
-<p>To move 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> to a new location, <dfn id=preserving-ranges>preserving ranges</dfn>, remove
-the <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> from its original <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> (if any), then insert it in the new
-location. In doing so, however, ignore the regular <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#range-mutation-rules>range mutation rules</a>, and
-instead follow these rules:
-
-<ol>
- <li>Let <var title="">node</var> be the moved <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>, <var title="">old parent</var> and
- <var title="">old index</var> be the old <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> (which may be null) and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>,
- and <var title="">new parent</var> and <var title="">new index</var> be the new <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> and
- <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>.
-
- <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is the same as or a <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>descendant</a> of
- <var title="">node</var>, leave it unchanged, so it moves to the new location. <!--
- This is actually implicit, but I state it anyway for completeness. -->
-
- <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">new parent</var> and its
- <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is greater than <var title="">new index</var>, add one to its
- <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
-
- <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">old parent</var> and its
- <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is <var title="">old index</var> or <var title="">old index</var> + 1, set its
- <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> to <var title="">new parent</var> and add <var title="">new index</var> −
- <var title="">old index</var> to its <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
-
- <li>If a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point title=concept-boundary-point>boundary point</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> is <var title="">old parent</var> and its
- <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a> is greater than <var title="">old index</var> + 1, subtract one from its
- <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
-</ol>
-
<h3 id=wrapping-a-list-of-nodes><span class=secno>5.2 </span>Wrapping a list of nodes</h3>
@@ -1410,6 +1410,9 @@
display property computes to something other than "inline", "inline-block", or
"inline-table"; or any <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> that is not <a href=#editable>editable</a>.
+<p class=XXX>Probably want to redefine unwrappable node in terms of allowed
+children or something.
+
<p>A <dfn id=modifiable-element>modifiable element</dfn> is a <code class=external data-anolis-spec=html title="the b element"><a href=http://www.whatwg.org/html/#the-b-element>b</a></code>, <code class=external data-anolis-spec=html title="the em element"><a href=http://www.whatwg.org/html/#the-em-element>em</a></code>, <code class=external data-anolis-spec=html title="the i element"><a href=http://www.whatwg.org/html/#the-i-element>i</a></code>, <code class=external data-anolis-spec=html title="the s element"><a href=http://www.whatwg.org/html/#the-s-element>s</a></code>, <code class=external data-anolis-spec=html title="the span element"><a href=http://www.whatwg.org/html/#the-span-element>span</a></code>,
<code class=external data-anolis-spec=html title="the strike element"><a href=http://www.whatwg.org/html/#the-strike-element>strike</a></code>, <code class=external data-anolis-spec=html title="the strong element"><a href=http://www.whatwg.org/html/#the-strong-element>strong</a></code>, <code class=external data-anolis-spec=html title="the sub and sup elements"><a href=http://www.whatwg.org/html/#the-sub-and-sup-elements>sub</a></code>, <code class=external data-anolis-spec=html title="the sub and sup elements"><a href=http://www.whatwg.org/html/#the-sub-and-sup-elements>sup</a></code>, or <code class=external data-anolis-spec=html title="the u element"><a href=http://www.whatwg.org/html/#the-u-element>u</a></code> element with no attributes
except possibly <code class=external data-anolis-spec=html title="the style attribute"><a href=http://www.whatwg.org/html/#the-style-attribute>style</a></code>; or a <code class=external data-anolis-spec=html title=font><a href=http://www.whatwg.org/html/#font>font</a></code> element with no attributes except
@@ -2474,282 +2477,7 @@
<!-- I'd have expected the value to be the URL, but guess not. -->
-<h3 id=the-delete-command><span class=secno>6.11 </span><dfn>The <code title="">delete</code> command</dfn></h3>
-<!-- Not really specifically "inline", but I didn't want to create a new
-section just for it. -->
-
-<p><a href=#action>Action</a>:
-
-<ol>
- <li>If the <a href=#active-range>active range</a> is not <code class=external data-anolis-spec=domrange title=dom-Range-collapsed><a href=http://html5.org/specs/dom-range.html#dom-range-collapsed>collapsed</a></code>, <a href=#delete-the-contents>delete the contents</a>
- of the <a href=#active-range>active range</a> and abort these steps.
-
- <p class=XXX>Maybe we sometimes want to do the following even if it isn't
- collapsed? WebKit seems to do some normalization on the range before
- deciding whether it's collapsed, and that sounds like a good idea.
-
- <li>Let <var title="">node</var> and <var title="">offset</var> be 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 class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
-
- <!-- First go up as high as possible within the current block, then drill
- down to the lowest possible level, in the hopes that we'll wind up at the end
- of a text node, or maybe in a br or hr. -->
- <li>Repeat the following steps:
-
- <ol>
- <!--
- If there's an invisible node somewhere, Firefox 5.0a2 removes that node and
- then stops, so each backspace removes one invisible node. All others
- remove the invisible node and then continue on looking for something
- visible to remove. The spec follows the latter behavior, since it makes
- more sense to the user. Of course, the definition of "invisible node" is
- not necessarily anything like the spec's.
- -->
- <li>If <var title="">offset</var> is zero and <var title="">node</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code>
- is an <a href=#editable>editable</a> <a href=#invisible-node>invisible node</a>, remove
- <var title="">node</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> 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>Otherwise, if <var title="">node</var> has a <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> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
- <var title="">offset</var> − 1 and that <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> is an <a href=#editable>editable</a>
- <a href=#invisible-node>invisible node</a>, remove that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> from <var title="">node</var>,
- then subtract one from <var title="">offset</var>.
-
- <li>Otherwise, if <var title="">offset</var> is zero and <var title="">node</var> is not a
- <a href=#prohibited-paragraph-child>prohibited paragraph child</a>, or if <var title="">node</var> is an
- <a href=#invisible-node>invisible node</a>, set <var title="">offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of
- <var title="">node</var>, then set <var title="">node</var> to 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>Otherwise, if <var title="">node</var> has a <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> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
- <var title="">offset</var> − 1 and that <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> is not a <a href=#prohibited-paragraph-child>prohibited
- paragraph child</a> or a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or an <code class=external data-anolis-spec=html title="the img element"><a href=http://www.whatwg.org/html/#the-img-element>img</a></code>, set <var title="">node</var> to
- that <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>, then set <var title="">offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of
- <var title="">node</var>.
-
- <li>Otherwise, break from this loop.
- </ol>
-
- <!--
- At this point, node cannot be an invisible node. There are three cases:
-
- 1) offset is zero and node is a prohibited paragraph child. Then we'll
- usually merge with the previous block if one exists.
-
- 2) offset is not zero, node is not a prohibited paragraph child, and node
- does not have a child with index offset - 1. The only way this is possible
- is if node has a length greater than zero but no children, which implies it's
- a text or comment or PI. Comments and PIs are invisible nodes, so it must be
- a text node. We delete the previous character.
-
- 3) offset is not zero, and the child of node with index offset - 1 is a
- prohibited paragraph child or a br or an img. Then we'll usually merge the
- offsetth child of node with the last descendant of the offset - 1st.
- -->
-
- <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 and <var title="">offset</var> is not zero,
- call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
- Then <a href=#delete-the-contents>delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
- (<var title="">node</var>, <var title="">offset</var> − 1) 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>
- (<var title="">node</var>, <var title="">offset</var>) and abort these steps.
-
- <!-- At the time of this writing, this should be impossible. -->
- <li>If <var title="">node</var> is not a <a href=#prohibited-paragraph-child>prohibited paragraph child</a>,
- abort these steps.
-
- <li>If <var title="">node</var> has a <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> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">offset</var>
- − 1 and that <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> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or <code class=external data-anolis-spec=html title="the hr element"><a href=http://www.whatwg.org/html/#the-hr-element>hr</a></code> or <code class=external data-anolis-spec=html title="the img element"><a href=http://www.whatwg.org/html/#the-img-element>img</a></code>, call
- <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
- Then <a href=#delete-the-contents>delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
- (<var title="">node</var>, <var title="">offset</var> − 1) 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>
- (<var title="">node</var>, <var title="">offset</var>) and abort these steps.
-
- <!--
- If we're at the beginning of a list, we want to outdent the first list item.
- This doesn't actually match anyone or anything. Word 2007 and OpenOffice.org
- 3.2.1 Ubuntu just remove the list marker, which is weird and doesn't map well
- to HTML. Browsers tend to just merge with the preceding block, which isn't
- expected.
- -->
- <li>If <var title="">node</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code> and is 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 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>
- <li>Let <var title="">items</var> be a list of all <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>s that are
- <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestors</a> of <var title="">node</var>.
-
- <li><a href=#normalize-sublists>Normalize sublists</a> of each <var title="">item</var> in
- <var title="">items</var>.
-
- <li><a href=#split-the-parent>Split the parent</a> of the one-<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> list consisting of
- <var title="">node</var>.
-
- <li><a href=#fix-disallowed-ancestors>Fix disallowed ancestors</a> of <var title="">node</var>.
-
- <li>Abort these steps.
- </ol>
-
- <!-- By this point, we're almost certainly going to merge something, and the
- only question is what. -->
- <li>Let <var title="">start node</var> equal <var title="">node</var> and let <var title="">start
- offset</var> equal <var title="">offset</var>.
-
- <li>While <var title="">start offset</var> is zero, set <var title="">start offset</var> to the
- <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <var title="">start node</var> and then set <var title="">start node</var> to 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>.
-
- <p class=XXX>The node at index start offset − 1 might be an invisible
- node.
-
- <!--
- At the beginning of an indented block, outdent it, similar to a list item.
- Browsers don't do this, word processors do.
- -->
- <li>If <var title="">offset</var> is zero, and <var title="">node</var> has an
- <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container title="ancestor container">ancestor container</a> that is both a <a href=#potential-indentation-element>potential indentation
- element</a> and a <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>descendant</a> of <var title="">start node</var>:
-
- <p class=XXX>This copy-pastes from the outdent command action. I'm also not
- totally sure it's correct.
-
- <ol>
- <li><a href=#block-extend>Block-extend</a> the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> whose <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> are both (<var title="">node</var>, 0), and let <var title="">new range</var> be
- the result.
-
- <li>Let <var title="">node list</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.
-
- <li>For each <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> <var title="">current node</var> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new
- range</var>, append <var title="">current node</var> to <var title="">node list</var> if the
- last member of <var title="">node list</var> (if any) is not an <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of
- <var title="">current node</var>, and <var title="">current node</var> is
- <a href=#editable>editable</a> but has no <a href=#editable>editable</a> <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>.
-
- <li><a href=#outdent>Outdent</a> each <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> in <var title="">node list</var>.
-
- <li>Abort these steps.
- </ol>
-
- <!--
- This is to avoid stripping a line break from
-
- foo<br><br><table><tr><td>[]bar</table>
-
- and similarly for <hr>. We should just do nothing here.
- -->
- <li>If the <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 node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
- offset</var> is a <code class=external data-anolis-spec=html title="the table element"><a href=http://www.whatwg.org/html/#the-table-element>table</a></code>, abort these steps.
-
- <!--
- If you try backspacing into a table, select it. This doesn't match any
- browser; it matches the recommendation of the "behavior when typing in
- contentEditable elements" document. The idea is that then you can delete it
- with a second backspace.
- -->
- <li>If <var title="">start node</var> has a <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> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
- offset</var> − 1, and that <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> is a <code class=external data-anolis-spec=html title="the table element"><a href=http://www.whatwg.org/html/#the-table-element>table</a></code>:
-
- <ol>
- <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">start node</var>, <var title="">start offset</var>
- − 1)</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
-
- <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-extend><a href=http://html5.org/specs/dom-range.html#dom-selection-extend>extend(<var title="">start node</var>, <var title="">start offset</var>)</a></code> on the
- <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
-
- <li>Abort these steps.
- </ol>
-
- <!--
- Special case:
-
- <p>foo</p><br><p>[]bar</p>
- -> <p>foo</p><p>[]bar</p>
-
- and likewise for <hr>. But with <img> we merge like in other cases:
-
- <p>foo</p><img><p>[]bar</p>
- -> <p>foo</p><img>[]bar.
-
- Browsers don't do this consistently. Firefox 5.0a2 doesn't seem to do it at
- all.
- -->
- <li>If <var title="">offset</var> is zero; and either the <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
- node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> minus one is an <code class=external data-anolis-spec=html title="the hr element"><a href=http://www.whatwg.org/html/#the-hr-element>hr</a></code>, or
- the <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> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> whose <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> is either a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or not
- an <a href=#inline-node>inline node</a>:
-
- <ol>
- <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the
- <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
-
- <li><a href=#delete-the-contents>Delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
- (<var title="">start node</var>, <var title="">start offset</var> − 1) 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>
- (<var title="">start node</var>, <var title="">start offset</var>).
-
- <li>Abort these steps.
- </ol>
-
- <!--
- If you try backspacing out of a list item, merge it with the previous item,
- but add a line break. Then you have to backspace again if you really want
- them to be on the same line. This matches Word 2007 and OpenOffice.org 3.2.1
- Ubuntu, and also matches "behavior when typing in contentEditable elements",
- but does not match any browser.
-
- Note that this behavior is quite different from what happens if you actually
- select the linebreak in between the two lines. In that case, the blocks are
- merged as normal.
-
- Also note that hitting backspace twice will merge with the previous item.
- This matches OO.org, but Word will outdent the item on subsequent backspaces.
- Word's behavior doesn't fit well with the way lists work in HTML, and we
- probably don't want it.
- -->
- <li>If the <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 node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
- offset</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, and that <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>'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 an <a href=#inline-node>inline node</a>, and <var title="">start offset</var> is
- not zero:
-
- <ol>
- <li>Let <var title="">previous item</var> be the <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 node</var>
- with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> minus one.
-
- <!-- If the last child is already a br, we only need to append one extra
- br. Otherwise we need to append two, since the first will do nothing. -->
- <li>If <var title="">previous item</var>'s <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> is an <a href=#inline-node>inline
- node</a> other than a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, 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="">previous item</var>.
-
- <li>If <var title="">previous item</var>'s <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> is an <a href=#inline-node>inline
- node</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="">previous item</var>.
- </ol>
-
- <!--
- When merging adjacent list items, make sure we only merge the items
- themselves, not any block children. We want <li><p>foo<li><p>bar to become
- <li><p>foo<p>bar, not <li><p>foo<br>bar or <li><p>foobar.
- -->
- <li>If the <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 node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
- offset</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, and its <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> is
- also an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, set <var title="">start node</var> to its
- <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> − 1, then set
- <var title="">start offset</var> to <var title="">start node</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a>, then set
- <var title="">node</var> to <var title="">start node</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-nextSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling>nextSibling</a></code>, then set
- <var title="">offset</var> to 0.
-
- <!-- General block-merging case. -->
- <li>Otherwise, while <var title="">start node</var> has a <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> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
- <var title="">start offset</var> minus one, set <var title="">start node</var> to that
- <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>, then set <var title="">start offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of
- <var title="">start node</var>.
-
- <li><a href=#delete-the-contents>Delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
- (<var title="">start node</var>, <var title="">start offset</var>) 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>
- (<var title="">node</var>, <var title="">offset</var>).
-</ol>
-
-
-<h3 id=the-fontname-command><span class=secno>6.12 </span><dfn>The <code title="">fontName</code> command</dfn></h3>
+<h3 id=the-fontname-command><span class=secno>6.11 </span><dfn>The <code title="">fontName</code> command</dfn></h3>
<p><a href=#action>Action</a>: <a href=#decompose>Decompose</a> the <a href=#active-range>active range</a>,
then <a href=#set-the-value>set the value</a> of each returned <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> to <var title="">value</var>.
@@ -2798,7 +2526,7 @@
<p><a href=#relevant-css-property>Relevant CSS property</a>: "font-family"
-<h3 id=the-fontsize-command><span class=secno>6.13 </span><dfn>The <code title="">fontSize</code> command</dfn></h3>
+<h3 id=the-fontsize-command><span class=secno>6.12 </span><dfn>The <code title="">fontSize</code> command</dfn></h3>
<p><a href=#action>Action</a>:
@@ -2905,7 +2633,7 @@
<p><a href=#relevant-css-property>Relevant CSS property</a>: "font-size"
-<h3 id=the-forecolor-command><span class=secno>6.14 </span><dfn>The <code title="">foreColor</code> command</dfn></h3>
+<h3 id=the-forecolor-command><span class=secno>6.13 </span><dfn>The <code title="">foreColor</code> command</dfn></h3>
<p><a href=#action>Action</a>:
@@ -2990,7 +2718,7 @@
<p><a href=#relevant-css-property>Relevant CSS property</a>: "color"
-<h3 id=the-hilitecolor-command><span class=secno>6.15 </span><dfn>The <code title="">hiliteColor</code> command</dfn></h3>
+<h3 id=the-hilitecolor-command><span class=secno>6.14 </span><dfn>The <code title="">hiliteColor</code> command</dfn></h3>
<!-- IE 9 RC doesn't support this. It uses backColor instead, but Gecko and
Opera treat that differently, while all non-IE browsers treat hiliteColor the
same, so I'm standardizing hiliteColor as the way to highlight text.
@@ -3029,7 +2757,7 @@
<p><a href=#relevant-css-property>Relevant CSS property</a>: "background-color"
-<h3 id=the-inserthtml-command><span class=secno>6.16 </span><dfn>The <code title="">insertHTML</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>:
@@ -3059,7 +2787,7 @@
</ol>
-<h3 id=the-insertimage-command><span class=secno>6.17 </span><dfn>The <code title="">insertImage</code> command</dfn></h3>
+<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>:
@@ -3095,7 +2823,7 @@
</ol>
-<h3 id=the-italic-command><span class=secno>6.18 </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
@@ -3111,7 +2839,7 @@
<p><a href=#relevant-css-property>Relevant CSS property</a>: "font-style"
-<h3 id=the-removeformat-command><span class=secno>6.19 </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.
@@ -3232,7 +2960,7 @@
</ol>
-<h3 id=the-strikethrough-command><span class=secno>6.20 </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
@@ -3248,7 +2976,7 @@
<p><a href=#value>Value</a>: Always the empty string.
-<h3 id=the-subscript-command><span class=secno>6.21 </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>:
@@ -3273,7 +3001,7 @@
<p><a href=#relevant-css-property>Relevant CSS property</a>: "vertical-align"
-<h3 id=the-superscript-command><span class=secno>6.22 </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>:
@@ -3298,7 +3026,7 @@
<p><a href=#relevant-css-property>Relevant CSS property</a>: "vertical-align"
-<h3 id=the-underline-command><span class=secno>6.23 </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
@@ -3360,7 +3088,7 @@
<p><a href=#value>Value</a>: Always the empty string.
-<h3 id=the-unlink-command><span class=secno>6.24 </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>:
@@ -3745,6 +3473,9 @@
<h3 id=block-formatting-a-node-list><span class=secno>7.4 </span>Block-formatting a node list</h3>
+<p class=XXX>Why is this a separate section? There's only one caller.
+Probably want to merge it back.
+
<p>To <dfn id=block-format>block-format</dfn> 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> <var title="">input nodes</var> to a
string <var title="">value</var>:
@@ -4717,7 +4448,280 @@
</ol>
-<h3 id=the-formatblock-command><span class=secno>7.8 </span><dfn>The <code title="">formatBlock</code> command</dfn></h3>
+<h3 id=the-delete-command><span class=secno>7.8 </span><dfn>The <code title="">delete</code> command</dfn></h3>
+
+<p><a href=#action>Action</a>:
+
+<ol>
+ <li>If the <a href=#active-range>active range</a> is not <code class=external data-anolis-spec=domrange title=dom-Range-collapsed><a href=http://html5.org/specs/dom-range.html#dom-range-collapsed>collapsed</a></code>, <a href=#delete-the-contents>delete the contents</a>
+ of the <a href=#active-range>active range</a> and abort these steps.
+
+ <p class=XXX>Maybe we sometimes want to do the following even if it isn't
+ collapsed? WebKit seems to do some normalization on the range before
+ deciding whether it's collapsed, and that sounds like a good idea.
+
+ <li>Let <var title="">node</var> and <var title="">offset</var> be 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 class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a> and <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-offset title=concept-boundary-point-offset>offset</a>.
+
+ <!-- First go up as high as possible within the current block, then drill
+ down to the lowest possible level, in the hopes that we'll wind up at the end
+ of a text node, or maybe in a br or hr. -->
+ <li>Repeat the following steps:
+
+ <ol>
+ <!--
+ If there's an invisible node somewhere, Firefox 5.0a2 removes that node and
+ then stops, so each backspace removes one invisible node. All others
+ remove the invisible node and then continue on looking for something
+ visible to remove. The spec follows the latter behavior, since it makes
+ more sense to the user. Of course, the definition of "invisible node" is
+ not necessarily anything like the spec's.
+ -->
+ <li>If <var title="">offset</var> is zero and <var title="">node</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code>
+ is an <a href=#editable>editable</a> <a href=#invisible-node>invisible node</a>, remove
+ <var title="">node</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> 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>Otherwise, if <var title="">node</var> has a <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> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+ <var title="">offset</var> − 1 and that <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> is an <a href=#editable>editable</a>
+ <a href=#invisible-node>invisible node</a>, remove that <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> from <var title="">node</var>,
+ then subtract one from <var title="">offset</var>.
+
+ <li>Otherwise, if <var title="">offset</var> is zero and <var title="">node</var> is not a
+ <a href=#prohibited-paragraph-child>prohibited paragraph child</a>, or if <var title="">node</var> is an
+ <a href=#invisible-node>invisible node</a>, set <var title="">offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of
+ <var title="">node</var>, then set <var title="">node</var> to 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>Otherwise, if <var title="">node</var> has a <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> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+ <var title="">offset</var> − 1 and that <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> is not a <a href=#prohibited-paragraph-child>prohibited
+ paragraph child</a> or a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or an <code class=external data-anolis-spec=html title="the img element"><a href=http://www.whatwg.org/html/#the-img-element>img</a></code>, set <var title="">node</var> to
+ that <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>, then set <var title="">offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of
+ <var title="">node</var>.
+
+ <li>Otherwise, break from this loop.
+ </ol>
+
+ <!--
+ At this point, node cannot be an invisible node. There are three cases:
+
+ 1) offset is zero and node is a prohibited paragraph child. Then we'll
+ usually merge with the previous block if one exists.
+
+ 2) offset is not zero, node is not a prohibited paragraph child, and node
+ does not have a child with index offset - 1. The only way this is possible
+ is if node has a length greater than zero but no children, which implies it's
+ a text or comment or PI. Comments and PIs are invisible nodes, so it must be
+ a text node. We delete the previous character.
+
+ 3) offset is not zero, and the child of node with index offset - 1 is a
+ prohibited paragraph child or a br or an img. Then we'll usually merge the
+ offsetth child of node with the last descendant of the offset - 1st.
+ -->
+
+ <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 and <var title="">offset</var> is not zero,
+ call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+ Then <a href=#delete-the-contents>delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
+ (<var title="">node</var>, <var title="">offset</var> − 1) 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>
+ (<var title="">node</var>, <var title="">offset</var>) and abort these steps.
+
+ <!-- At the time of this writing, this should be impossible. -->
+ <li>If <var title="">node</var> is not a <a href=#prohibited-paragraph-child>prohibited paragraph child</a>,
+ abort these steps.
+
+ <li>If <var title="">node</var> has a <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> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">offset</var>
+ − 1 and that <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> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or <code class=external data-anolis-spec=html title="the hr element"><a href=http://www.whatwg.org/html/#the-hr-element>hr</a></code> or <code class=external data-anolis-spec=html title="the img element"><a href=http://www.whatwg.org/html/#the-img-element>img</a></code>, call
+ <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+ Then <a href=#delete-the-contents>delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
+ (<var title="">node</var>, <var title="">offset</var> − 1) 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>
+ (<var title="">node</var>, <var title="">offset</var>) and abort these steps.
+
+ <!--
+ If we're at the beginning of a list, we want to outdent the first list item.
+ This doesn't actually match anyone or anything. Word 2007 and OpenOffice.org
+ 3.2.1 Ubuntu just remove the list marker, which is weird and doesn't map well
+ to HTML. Browsers tend to just merge with the preceding block, which isn't
+ expected.
+ -->
+ <li>If <var title="">node</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code> and is 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 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>
+ <li>Let <var title="">items</var> be a list of all <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>s that are
+ <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestors</a> of <var title="">node</var>.
+
+ <li><a href=#normalize-sublists>Normalize sublists</a> of each <var title="">item</var> in
+ <var title="">items</var>.
+
+ <li><a href=#split-the-parent>Split the parent</a> of the one-<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> list consisting of
+ <var title="">node</var>.
+
+ <li><a href=#fix-disallowed-ancestors>Fix disallowed ancestors</a> of <var title="">node</var>.
+
+ <li>Abort these steps.
+ </ol>
+
+ <!-- By this point, we're almost certainly going to merge something, and the
+ only question is what. -->
+ <li>Let <var title="">start node</var> equal <var title="">node</var> and let <var title="">start
+ offset</var> equal <var title="">offset</var>.
+
+ <li>While <var title="">start offset</var> is zero, set <var title="">start offset</var> to the
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of <var title="">start node</var> and then set <var title="">start node</var> to 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>.
+
+ <p class=XXX>The node at index start offset − 1 might be an invisible
+ node.
+
+ <!--
+ At the beginning of an indented block, outdent it, similar to a list item.
+ Browsers don't do this, word processors do.
+ -->
+ <li>If <var title="">offset</var> is zero, and <var title="">node</var> has an
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container title="ancestor container">ancestor container</a> that is both a <a href=#potential-indentation-element>potential indentation
+ element</a> and a <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>descendant</a> of <var title="">start node</var>:
+
+ <p class=XXX>This copy-pastes from the outdent command action. I'm also not
+ totally sure it's correct.
+
+ <ol>
+ <li><a href=#block-extend>Block-extend</a> the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> whose <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> are both (<var title="">node</var>, 0), and let <var title="">new range</var> be
+ the result.
+
+ <li>Let <var title="">node list</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.
+
+ <li>For each <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> <var title="">current node</var> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in <var title="">new
+ range</var>, append <var title="">current node</var> to <var title="">node list</var> if the
+ last member of <var title="">node list</var> (if any) is not an <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of
+ <var title="">current node</var>, and <var title="">current node</var> is
+ <a href=#editable>editable</a> but has no <a href=#editable>editable</a> <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>.
+
+ <li><a href=#outdent>Outdent</a> each <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> in <var title="">node list</var>.
+
+ <li>Abort these steps.
+ </ol>
+
+ <!--
+ This is to avoid stripping a line break from
+
+ foo<br><br><table><tr><td>[]bar</table>
+
+ and similarly for <hr>. We should just do nothing here.
+ -->
+ <li>If the <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 node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
+ offset</var> is a <code class=external data-anolis-spec=html title="the table element"><a href=http://www.whatwg.org/html/#the-table-element>table</a></code>, abort these steps.
+
+ <!--
+ If you try backspacing into a table, select it. This doesn't match any
+ browser; it matches the recommendation of the "behavior when typing in
+ contentEditable elements" document. The idea is that then you can delete it
+ with a second backspace.
+ -->
+ <li>If <var title="">start node</var> has a <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> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
+ offset</var> − 1, and that <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> is a <code class=external data-anolis-spec=html title="the table element"><a href=http://www.whatwg.org/html/#the-table-element>table</a></code>:
+
+ <ol>
+ <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">start node</var>, <var title="">start offset</var>
+ − 1)</a></code> on the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+
+ <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-extend><a href=http://html5.org/specs/dom-range.html#dom-selection-extend>extend(<var title="">start node</var>, <var title="">start offset</var>)</a></code> on the
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#context-object>context object</a>'s <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+
+ <li>Abort these steps.
+ </ol>
+
+ <!--
+ Special case:
+
+ <p>foo</p><br><p>[]bar</p>
+ -> <p>foo</p><p>[]bar</p>
+
+ and likewise for <hr>. But with <img> we merge like in other cases:
+
+ <p>foo</p><img><p>[]bar</p>
+ -> <p>foo</p><img>[]bar.
+
+ Browsers don't do this consistently. Firefox 5.0a2 doesn't seem to do it at
+ all.
+ -->
+ <li>If <var title="">offset</var> is zero; and either the <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
+ node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> minus one is an <code class=external data-anolis-spec=html title="the hr element"><a href=http://www.whatwg.org/html/#the-hr-element>hr</a></code>, or
+ the <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> is a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> whose <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> is either a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code> or not
+ an <a href=#inline-node>inline node</a>:
+
+ <ol>
+ <li>Call <code class=external data-anolis-spec=domrange title=dom-Selection-collapse><a href=http://html5.org/specs/dom-range.html#dom-selection-collapse>collapse(<var title="">node</var>, <var title="">offset</var>)</a></code> on the
+ <code class=external data-anolis-spec=domrange><a href=http://html5.org/specs/dom-range.html#selection>Selection</a></code>.
+
+ <li><a href=#delete-the-contents>Delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
+ (<var title="">start node</var>, <var title="">start offset</var> − 1) 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>
+ (<var title="">start node</var>, <var title="">start offset</var>).
+
+ <li>Abort these steps.
+ </ol>
+
+ <!--
+ If you try backspacing out of a list item, merge it with the previous item,
+ but add a line break. Then you have to backspace again if you really want
+ them to be on the same line. This matches Word 2007 and OpenOffice.org 3.2.1
+ Ubuntu, and also matches "behavior when typing in contentEditable elements",
+ but does not match any browser.
+
+ Note that this behavior is quite different from what happens if you actually
+ select the linebreak in between the two lines. In that case, the blocks are
+ merged as normal.
+
+ Also note that hitting backspace twice will merge with the previous item.
+ This matches OO.org, but Word will outdent the item on subsequent backspaces.
+ Word's behavior doesn't fit well with the way lists work in HTML, and we
+ probably don't want it.
+ -->
+ <li>If the <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 node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
+ offset</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, and that <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>'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 an <a href=#inline-node>inline node</a>, and <var title="">start offset</var> is
+ not zero:
+
+ <ol>
+ <li>Let <var title="">previous item</var> be the <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 node</var>
+ with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> minus one.
+
+ <!-- If the last child is already a br, we only need to append one extra
+ br. Otherwise we need to append two, since the first will do nothing. -->
+ <li>If <var title="">previous item</var>'s <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> is an <a href=#inline-node>inline
+ node</a> other than a <code class=external data-anolis-spec=html title="the br element"><a href=http://www.whatwg.org/html/#the-br-element>br</a></code>, 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="">previous item</var>.
+
+ <li>If <var title="">previous item</var>'s <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> is an <a href=#inline-node>inline
+ node</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="">previous item</var>.
+ </ol>
+
+ <!--
+ When merging adjacent list items, make sure we only merge the items
+ themselves, not any block children. We want <li><p>foo<li><p>bar to become
+ <li><p>foo<p>bar, not <li><p>foo<br>bar or <li><p>foobar.
+ -->
+ <li>If the <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 node</var> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start
+ offset</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, and its <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> is
+ also an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> or <code class=external data-anolis-spec=html title="the dt element"><a href=http://www.whatwg.org/html/#the-dt-element>dt</a></code> or <code class=external data-anolis-spec=html title="the dd element"><a href=http://www.whatwg.org/html/#the-dd-element>dd</a></code>, set <var title="">start node</var> to its
+ <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> <var title="">start offset</var> − 1, then set
+ <var title="">start offset</var> to <var title="">start node</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a>, then set
+ <var title="">node</var> to <var title="">start node</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-nextSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling>nextSibling</a></code>, then set
+ <var title="">offset</var> to 0.
+
+ <!-- General block-merging case. -->
+ <li>Otherwise, while <var title="">start node</var> has a <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> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>
+ <var title="">start offset</var> minus one, set <var title="">start node</var> to that
+ <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>, then set <var title="">start offset</var> to the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-node-length title=concept-node-length>length</a> of
+ <var title="">start node</var>.
+
+ <li><a href=#delete-the-contents>Delete the contents</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
+ (<var title="">start node</var>, <var title="">start offset</var>) 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>
+ (<var title="">node</var>, <var title="">offset</var>).
+</ol>
+
+
+<h3 id=the-formatblock-command><span class=secno>7.9 </span><dfn>The <code title="">formatBlock</code> command</dfn></h3>
<!--
Tested browser versions: IE9, Firefox 4.0, Chrome 13 dev, Opera 11.10.
@@ -4801,7 +4805,7 @@
</ol>
-<h3 id=the-indent-command><span class=secno>7.9 </span><dfn>The <code title="">indent</code> command</dfn></h3>
+<h3 id=the-indent-command><span class=secno>7.10 </span><dfn>The <code title="">indent</code> command</dfn></h3>
<!--
IE9: Outputs <blockquote style="margin-right: 0px" dir="ltr">, or when
surrounding RTL blocks, <blockquote style="margin-left: 0px" dir="rtl">. The
@@ -4904,7 +4908,7 @@
</ol>
-<h3 id=the-inserthorizontalrule-command><span class=secno>7.10 </span><dfn>The <code title="">insertHorizontalRule</code> command</dfn></h3>
+<h3 id=the-inserthorizontalrule-command><span class=secno>7.11 </span><dfn>The <code title="">insertHorizontalRule</code> command</dfn></h3>
<p><a href=#action>Action</a>:
@@ -4958,13 +4962,13 @@
</ol>
-<h3 id=the-insertorderedlist-command><span class=secno>7.11 </span><dfn>The <code title="">insertOrderedList</code> command</dfn></h3>
+<h3 id=the-insertorderedlist-command><span class=secno>7.12 </span><dfn>The <code title="">insertOrderedList</code> command</dfn></h3>
<p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
"ol".
-<h3 id=the-insertparagraph-command><span class=secno>7.12 </span><dfn>The <code title="">insertParagraph</code> command</dfn></h3>
+<h3 id=the-insertparagraph-command><span class=secno>7.13 </span><dfn>The <code title="">insertParagraph</code> command</dfn></h3>
<!--
There are three major behaviors here. Firefox 5.0a2 behaves identically to
execCommand("formatBlock", false, "p"), which is not really useful. IE9
@@ -5214,37 +5218,37 @@
</ol>
-<h3 id=the-insertunorderedlist-command><span class=secno>7.13 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
+<h3 id=the-insertunorderedlist-command><span class=secno>7.14 </span><dfn>The <code title="">insertUnorderedList</code> command</dfn></h3>
<p><a href=#action>Action</a>: <a href=#toggle-lists>Toggle lists</a> with <var title="">tag name</var>
"ul".
-<h3 id=the-justifycenter-command><span class=secno>7.14 </span><dfn>The <code title="">justifyCenter</code> command</dfn></h3>
+<h3 id=the-justifycenter-command><span class=secno>7.15 </span><dfn>The <code title="">justifyCenter</code> command</dfn></h3>
<p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
<var title="">alignment</var> "center".
-<h3 id=the-justifyfull-command><span class=secno>7.15 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
+<h3 id=the-justifyfull-command><span class=secno>7.16 </span><dfn>The <code title="">justifyFull</code> command</dfn></h3>
<p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
<var title="">alignment</var> "justify".
-<h3 id=the-justifyleft-command><span class=secno>7.16 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
+<h3 id=the-justifyleft-command><span class=secno>7.17 </span><dfn>The <code title="">justifyLeft</code> command</dfn></h3>
<p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
<var title="">alignment</var> "left".
-<h3 id=the-justifyright-command><span class=secno>7.17 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
+<h3 id=the-justifyright-command><span class=secno>7.18 </span><dfn>The <code title="">justifyRight</code> command</dfn></h3>
<p><a href=#action>Action</a>: <a href=#justify-the-selection>Justify the selection</a> with
<var title="">alignment</var> "right".
-<h3 id=the-outdent-command><span class=secno>7.18 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
+<h3 id=the-outdent-command><span class=secno>7.19 </span><dfn>The <code title="">outdent</code> command</dfn></h3>
<p><a href=#action>Action</a>:
--- a/implementation.js Tue Jun 14 13:46:20 2011 -0600
+++ b/implementation.js Tue Jun 14 14:37:27 2011 -0600
@@ -3,9 +3,19 @@
var htmlNamespace = "http://www.w3.org/1999/xhtml";
var cssStylingFlag = false;
-var defaultSingleLineContainerName = "p";
-
-// Utility functions
+
+// This is bad :(
+var globalRange = null;
+
+// Commands are stored in a dictionary where we call their actions and such
+var commands = {};
+
+///////////////////////////////////////////////////////////////////////////////
+////////////////////////////// Utility functions //////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+//@{
+
+
function nextNode(node) {
if (node.hasChildNodes()) {
return node.firstChild;
@@ -192,9 +202,14 @@
return ns === null
|| ns === htmlNamespace;
}
-
-
-// Functions for stuff in DOM Range
+//@}
+
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////// DOM Range functions /////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+//@{
+
function getNodeIndex(node) {
if (!node.parentNode) {
// No preceding siblings, so . . .
@@ -429,10 +444,17 @@
"currentColor": false,
}[color];
}
-
-
-// Things defined in the edit command spec (i.e., the interesting stuff)
-
+//@}
+
+
+//////////////////////////////////////////////////////////////////////////////
+/////////////////////////// Edit command functions ///////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////
+///// Common definitions /////
+//////////////////////////////
+//@{
// "An HTML element is an Element whose namespace is the HTML namespace."
//
@@ -461,6 +483,338 @@
&& ["inline", "inline-block", "inline-table"].indexOf(getComputedStyle(node).display) != -1));
}
+// "An editing host is a node that is either an Element with a contenteditable
+// attribute set to the true state, or the Element child of a Document whose
+// designMode is enabled."
+function isEditingHost(node) {
+ return node
+ && node.nodeType == Node.ELEMENT_NODE
+ && (node.contentEditable == "true"
+ || (node.parentNode
+ && node.parentNode.nodeType == Node.DOCUMENT_NODE
+ && node.parentNodedesignMode == "on"));
+}
+
+// "Something is editable if it is a node which is not an editing host, does
+// not have a contenteditable attribute set to the false state, and whose
+// parent is an editing host or editable."
+function isEditable(node) {
+ // This is slightly a lie, because we're excluding non-HTML elements with
+ // contentEditable attributes.
+ return node
+ && !isEditingHost(node)
+ && (node.nodeType != Node.ELEMENT_NODE || node.contentEditable != "false")
+ && (isEditingHost(node.parentNode) || isEditable(node.parentNode));
+}
+
+// Helper function, not defined in the spec
+function hasEditableDescendants(node) {
+ for (var i = 0; i < node.childNodes.length; i++) {
+ if (isEditable(node.childNodes[i])
+ || hasEditableDescendants(node.childNodes[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// "The editing host of node is null if node is neither editable nor an editing
+// host; node itself, if node is an editing host; or the nearest ancestor of
+// node that is an editing host, if node is editable."
+function getEditingHostOf(node) {
+ if (isEditingHost(node)) {
+ return node;
+ } else if (isEditable(node)) {
+ var ancestor = node.parentNode;
+ while (!isEditingHost(ancestor)) {
+ ancestor = ancestor.parentNode;
+ }
+ return ancestor;
+ } else {
+ return null;
+ }
+}
+
+// "Two nodes are in the same editing host if the editing host of the first is
+// non-null and the same as the editing host of the second."
+function inSameEditingHost(node1, node2) {
+ return getEditingHostOf(node1)
+ && getEditingHostOf(node1) == getEditingHostOf(node2);
+}
+
+// "A prohibited paragraph child name is "address", "article", "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"."
+var prohibitedParagraphChildNames = ["address", "article", "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",
+ "xmp"];
+
+// "A prohibited paragraph child is an HTML element whose local name is a
+// prohibited paragraph child name."
+function isProhibitedParagraphChild(node) {
+ return isHtmlElement(node, prohibitedParagraphChildNames);
+}
+
+// "A visible node is a node that either is a prohibited paragraph child, or a
+// Text node whose data is not empty, or a br or img, or any node with a
+// descendant that is a visible node."
+function isVisibleNode(node) {
+ if (!node) {
+ return false;
+ }
+ if (isProhibitedParagraphChild(node)
+ || (node.nodeType == Node.TEXT_NODE && node.length)
+ || isHtmlElement(node, ["br", "img"])) {
+ return true;
+ }
+ for (var i = 0; i < node.childNodes.length; i++) {
+ if (isVisibleNode(node.childNodes[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// "An invisible node is a node that is not a visible node."
+function isInvisibleNode(node) {
+ return node && !isVisibleNode(node);
+}
+
+//@}
+
+/////////////////////////////
+///// Common algorithms /////
+/////////////////////////////
+
+///// Assorted common algorithms /////
+//@{
+
+function isAllowedChild(child, parent_) {
+ // "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
+ // an HTML element with local name equal to one of those, and child is a
+ // Text node whose data does not consist solely of space characters, return
+ // false."
+ if ((["colgroup", "table", "tbody", "tfoot", "thead", "tr"].indexOf(parent_) != -1
+ || isHtmlElement(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"]))
+ && typeof child == "object"
+ && child.nodeType == Node.TEXT_NODE
+ && !/^[ \t\n\f\r]*$/.test(child.data)) {
+ return false;
+ }
+
+ // "If parent is "script", "style", "plaintext", or "xmp", or an HTML
+ // element with local name equal to one of those, and child is not a Text
+ // node, return false."
+ if ((["script", "style", "plaintext", "xmp"].indexOf(parent_) != -1
+ || isHtmlElement(parent_, ["script", "style", "plaintext", "xmp"]))
+ && (typeof child != "object" || child.nodeType != Node.TEXT_NODE)) {
+ return false;
+ }
+
+ // "If child is a Document, DocumentFragment, or DocumentType, return
+ // false."
+ if (typeof child == "object"
+ && (child.nodeType == Node.DOCUMENT_NODE
+ || child.nodeType == Node.DOCUMENT_FRAGMENT_NODE
+ || child.nodeType == Node.DOCUMENT_TYPE_NODE)) {
+ return false;
+ }
+
+ // "If child is an HTML element, set child to the local name of child."
+ if (isHtmlElement(child)) {
+ child = child.tagName.toLowerCase();
+ }
+
+ // "If child is not a string, return true."
+ if (typeof child != "string") {
+ return true;
+ }
+
+ // "If parent is an HTML element:"
+ if (isHtmlElement(parent_)) {
+ // "If child is "a", and parent or some ancestor of parent is an a,
+ // return false."
+ //
+ // "If child is a prohibited paragraph child name and parent or some
+ // ancestor of parent is a p, return false."
+ //
+ // "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
+ // some ancestor of parent is an HTML element with local name "h1",
+ // "h2", "h3", "h4", "h5", or "h6", return false."
+ var ancestor = parent_;
+ while (ancestor) {
+ if (child == "a" && isHtmlElement(ancestor, "a")) {
+ return false;
+ }
+ if (prohibitedParagraphChildNames.indexOf(child) != -1
+ && isHtmlElement(ancestor, "p")) {
+ return false;
+ }
+ if (/^h[1-6]$/.test(child)
+ && isHtmlElement(ancestor)
+ && /^H[1-6]$/.test(ancestor.tagName)) {
+ return false;
+ }
+ ancestor = ancestor.parentNode;
+ }
+
+ // "Let parent be the local name of parent."
+ parent_ = parent_.tagName.toLowerCase();
+ }
+
+ // "If parent is an Element or DocumentFragment, return true."
+ if (typeof parent_ == "object"
+ && (parent_.nodeType == Node.ELEMENT_NODE
+ || parent_.nodeType == Node.DOCUMENT_FRAGMENT_NODE)) {
+ return true;
+ }
+
+ // "If parent is not a string, return false."
+ if (typeof parent_ != "string") {
+ return false;
+ }
+
+ // "If parent is in the following table, then return true if child is
+ // listed as an allowed child, and false otherwise."
+ switch (parent_) {
+ case "colgroup":
+ return child == "col";
+ case "table":
+ return ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1;
+ case "tbody":
+ case "thead":
+ case "tfoot":
+ return ["td", "th", "tr"].indexOf(child) != -1;
+ case "tr":
+ return ["td", "th"].indexOf(child) != -1;
+ }
+
+ // "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
+ // "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
+ // false."
+ if (["body", "caption", "col", "colgroup", "frame", "frameset", "head",
+ "html", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1) {
+ return false;
+ }
+
+ // "If child is "dd" or "dt" and parent is not "dl", return false."
+ if (["dd", "dt"].indexOf(child) != -1
+ && parent_ != "dl") {
+ return false;
+ }
+
+ // "If child is "li" and parent is not "ol" or "ul", return false."
+ if (child == "li"
+ && parent_ != "ol"
+ && parent_ != "ul") {
+ return false;
+ }
+
+ // "If parent is in the following table and child is listed as a prohibited
+ // child, return false."
+ var table = [
+ [["a"], ["a"]],
+ [["dd", "dt"], ["dd", "dt"]],
+ [["h1", "h2", "h3", "h4", "h5", "h6"], ["h1", "h2", "h3", "h4", "h5", "h6"]],
+ [["li"], ["li"]],
+ [["nobr"], ["nobr"]],
+ [["p"], prohibitedParagraphChildNames],
+ [["td", "th"], ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]],
+ ];
+ for (var i = 0; i < table.length; i++) {
+ if (table[i][0].indexOf(parent_) != -1
+ && table[i][1].indexOf(child) != -1) {
+ return false;
+ }
+ }
+
+ // "Return true."
+ return true;
+}
+
+function movePreservingRanges(node, newParent, newIndex) {
+ // For convenience, I allow newIndex to be -1 to mean "insert at the end".
+ if (newIndex == -1) {
+ newIndex = newParent.childNodes.length;
+ }
+
+ // "When the user agent is to move a Node to a new location, preserving
+ // ranges, it must remove the Node from its original parent (if any), then
+ // insert it in the new location. In doing so, however, it must ignore the
+ // regular range mutation rules, and instead follow these rules:"
+
+ // "Let node be the moved Node, old parent and old index be the old parent
+ // (which may be null) and index, and new parent and new index be the new
+ // parent and index."
+ var oldParent = node.parentNode;
+ var oldIndex = getNodeIndex(node);
+
+ // We only even attempt to preserve the global range object, not every
+ // range out there (the latter is probably impossible).
+ var start = [globalRange.startContainer, globalRange.startOffset];
+ var end = [globalRange.endContainer, globalRange.endOffset];
+
+ // "If a boundary point's node is the same as or a descendant of node,
+ // leave it unchanged, so it moves to the new location."
+ //
+ // No modifications necessary.
+
+ // "If a boundary point's node is new parent and its offset is greater than
+ // new index, add one to its offset."
+ if (globalRange.startContainer == newParent
+ && globalRange.startOffset > newIndex) {
+ start[1]++;
+ }
+ if (globalRange.endContainer == newParent
+ && globalRange.endOffset > newIndex) {
+ end[1]++;
+ }
+
+ // "If a boundary point's node is old parent and its offset is old index or
+ // old index + 1, set its node to new parent and add new index − old index
+ // to its offset."
+ if (globalRange.startContainer == oldParent
+ && (globalRange.startOffset == oldIndex
+ || globalRange.startOffset == oldIndex + 1)) {
+ start[0] = newParent;
+ start[1] += newIndex - oldIndex;
+ }
+ if (globalRange.endContainer == oldParent
+ && (globalRange.endOffset == oldIndex
+ || globalRange.endOffset == oldIndex + 1)) {
+ end[0] = newParent;
+ end[1] += newIndex - oldIndex;
+ }
+
+ // "If a boundary point's node is old parent and its offset is greater than
+ // old index + 1, subtract one from its offset."
+ if (globalRange.startContainer == oldParent
+ && globalRange.startOffset > oldIndex + 1) {
+ start[1]--;
+ }
+ if (globalRange.endContainer == oldParent
+ && globalRange.endOffset > oldIndex + 1) {
+ end[1]--;
+ }
+
+ // Now actually move it and preserve the range.
+ if (newParent.childNodes.length == newIndex) {
+ newParent.appendChild(node);
+ } else {
+ newParent.insertBefore(node, newParent.childNodes[newIndex]);
+ }
+ globalRange.setStart(start[0], start[1]);
+ globalRange.setEnd(end[0], end[1]);
+}
+
function setTagName(element, newName) {
// "If element is an HTML element with local name equal to new name, return
// element."
@@ -499,6 +853,52 @@
return replacementElement;
}
+function removeExtraneousLineBreaksBefore(node) {
+ // "If node is not an Element, or it is an inline node, do nothing and
+ // abort these steps."
+ if (!node
+ || node.nodeType != Node.ELEMENT_NODE
+ || isInlineNode(node)) {
+ return;
+ }
+
+ // "If the previousSibling of node is a br, and the previousSibling of the
+ // previousSibling of node is an inline node that is not a br, remove the
+ // previousSibling of node from its parent."
+ if (isHtmlElement(node.previousSibling, "BR")
+ && isInlineNode(node.previousSibling.previousSibling)
+ && !isHtmlElement(node.previousSibling.previousSibling, "BR")) {
+ node.parentNode.removeChild(node.previousSibling);
+ }
+}
+
+function removeExtraneousLineBreaksAtTheEndOf(node) {
+ // "If node is not an Element, or it is an inline node, do nothing and
+ // abort these steps."
+ if (!node
+ || node.nodeType != Node.ELEMENT_NODE
+ || isInlineNode(node)) {
+ return;
+ }
+
+ // "If node has at least two children, and its last child is a br, and its
+ // second-to-last child is an inline node that is not a br, remove the last
+ // child of node from node."
+ if (node.childNodes.length >= 2
+ && isHtmlElement(node.lastChild, "BR")
+ && isInlineNode(node.lastChild.previousSibling)
+ && !isHtmlElement(node.lastChild.previousSibling, "BR")) {
+ node.removeChild(node.lastChild);
+ }
+}
+
+// "To remove extraneous line breaks from a node, first remove extraneous line
+// breaks before it, then remove extraneous line breaks at the end of it."
+function removeExtraneousLineBreaksFrom(node) {
+ removeExtraneousLineBreaksBefore(node);
+ removeExtraneousLineBreaksAtTheEndOf(node);
+}
+
function splitParent(nodeList) {
// "Let original parent be the parent of the first member of node list."
var originalParent = nodeList[0].parentNode;
@@ -613,6 +1013,11 @@
splitParent([].slice.call(node.childNodes));
}
+//@}
+
+///// Wrapping a list of nodes /////
+//@{
+
function wrap(nodeList, siblingCriteria, newParentInstructions) {
// "If node list is empty, or the first member of node list is not
// editable, return null and abort these steps."
@@ -759,14 +1164,10 @@
return newParent;
}
-// "This is defined to be the first range in the Selection given by calling
-// getSelection() on the context object, or null if there is no such range."
-function getActiveRange() {
- if (getSelection().rangeCount) {
- return getSelection().getRangeAt(0);
- }
- return null;
-}
+//@}
+
+///// Deleting the contents of a range /////
+//@{
function deleteContents(node1, offset1, node2, offset2) {
var range;
@@ -1194,298 +1595,15 @@
}
}
-function removeExtraneousLineBreaksBefore(node) {
- // "If node is not an Element, or it is an inline node, do nothing and
- // abort these steps."
- if (!node
- || node.nodeType != Node.ELEMENT_NODE
- || isInlineNode(node)) {
- return;
- }
-
- // "If the previousSibling of node is a br, and the previousSibling of the
- // previousSibling of node is an inline node that is not a br, remove the
- // previousSibling of node from its parent."
- if (isHtmlElement(node.previousSibling, "BR")
- && isInlineNode(node.previousSibling.previousSibling)
- && !isHtmlElement(node.previousSibling.previousSibling, "BR")) {
- node.parentNode.removeChild(node.previousSibling);
- }
-}
-
-function removeExtraneousLineBreaksAtTheEndOf(node) {
- // "If node is not an Element, or it is an inline node, do nothing and
- // abort these steps."
- if (!node
- || node.nodeType != Node.ELEMENT_NODE
- || isInlineNode(node)) {
- return;
- }
-
- // "If node has at least two children, and its last child is a br, and its
- // second-to-last child is an inline node that is not a br, remove the last
- // child of node from node."
- if (node.childNodes.length >= 2
- && isHtmlElement(node.lastChild, "BR")
- && isInlineNode(node.lastChild.previousSibling)
- && !isHtmlElement(node.lastChild.previousSibling, "BR")) {
- node.removeChild(node.lastChild);
- }
-}
-
-// "To remove extraneous line breaks from a node, first remove extraneous line
-// breaks before it, then remove extraneous line breaks at the end of it."
-function removeExtraneousLineBreaksFrom(node) {
- removeExtraneousLineBreaksBefore(node);
- removeExtraneousLineBreaksAtTheEndOf(node);
-}
-
-// "An editing host is a node that is either an Element with a contenteditable
-// attribute set to the true state, or the Element child of a Document whose
-// designMode is enabled."
-function isEditingHost(node) {
- return node
- && node.nodeType == Node.ELEMENT_NODE
- && (node.contentEditable == "true"
- || (node.parentNode
- && node.parentNode.nodeType == Node.DOCUMENT_NODE
- && node.parentNodedesignMode == "on"));
-}
-
-// "Something is editable if it is a node which is not an editing host, does
-// not have a contenteditable attribute set to the false state, and whose
-// parent is an editing host or editable."
-function isEditable(node) {
- // This is slightly a lie, because we're excluding non-HTML elements with
- // contentEditable attributes.
- return node
- && !isEditingHost(node)
- && (node.nodeType != Node.ELEMENT_NODE || node.contentEditable != "false")
- && (isEditingHost(node.parentNode) || isEditable(node.parentNode));
-}
-
-// "The editing host of node is null if node is neither editable nor an editing
-// host; node itself, if node is an editing host; or the nearest ancestor of
-// node that is an editing host, if node is editable."
-function getEditingHostOf(node) {
- if (isEditingHost(node)) {
- return node;
- } else if (isEditable(node)) {
- var ancestor = node.parentNode;
- while (!isEditingHost(ancestor)) {
- ancestor = ancestor.parentNode;
- }
- return ancestor;
- } else {
- return null;
- }
-}
-
-// "Two nodes are in the same editing host if the editing host of the first is
-// non-null and the same as the editing host of the second."
-function inSameEditingHost(node1, node2) {
- return getEditingHostOf(node1)
- && getEditingHostOf(node1) == getEditingHostOf(node2);
-}
-
-// "A prohibited paragraph child name is "address", "article", "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"."
-var prohibitedParagraphChildNames = ["address", "article", "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",
- "xmp"];
-
-// "A prohibited paragraph child is an HTML element whose local name is a
-// prohibited paragraph child name."
-function isProhibitedParagraphChild(node) {
- return isHtmlElement(node, prohibitedParagraphChildNames);
-}
-
-// "A visible node is a node that either is a prohibited paragraph child, or a
-// Text node whose data is not empty, or a br or img, or any node with a
-// descendant that is a visible node."
-function isVisibleNode(node) {
- if (!node) {
- return false;
- }
- if (isProhibitedParagraphChild(node)
- || (node.nodeType == Node.TEXT_NODE && node.length)
- || isHtmlElement(node, ["br", "img"])) {
- return true;
- }
- for (var i = 0; i < node.childNodes.length; i++) {
- if (isVisibleNode(node.childNodes[i])) {
- return true;
- }
- }
- return false;
-}
-
-// "An invisible node is a node that is not a visible node."
-function isInvisibleNode(node) {
- return node && !isVisibleNode(node);
-}
-
-function isAllowedChild(child, parent_) {
- // "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
- // an HTML element with local name equal to one of those, and child is a
- // Text node whose data does not consist solely of space characters, return
- // false."
- if ((["colgroup", "table", "tbody", "tfoot", "thead", "tr"].indexOf(parent_) != -1
- || isHtmlElement(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"]))
- && typeof child == "object"
- && child.nodeType == Node.TEXT_NODE
- && !/^[ \t\n\f\r]*$/.test(child.data)) {
- return false;
- }
-
- // "If parent is "script", "style", "plaintext", or "xmp", or an HTML
- // element with local name equal to one of those, and child is not a Text
- // node, return false."
- if ((["script", "style", "plaintext", "xmp"].indexOf(parent_) != -1
- || isHtmlElement(parent_, ["script", "style", "plaintext", "xmp"]))
- && (typeof child != "object" || child.nodeType != Node.TEXT_NODE)) {
- return false;
- }
-
- // "If child is a Document, DocumentFragment, or DocumentType, return
- // false."
- if (typeof child == "object"
- && (child.nodeType == Node.DOCUMENT_NODE
- || child.nodeType == Node.DOCUMENT_FRAGMENT_NODE
- || child.nodeType == Node.DOCUMENT_TYPE_NODE)) {
- return false;
- }
-
- // "If child is an HTML element, set child to the local name of child."
- if (isHtmlElement(child)) {
- child = child.tagName.toLowerCase();
- }
-
- // "If child is not a string, return true."
- if (typeof child != "string") {
- return true;
- }
-
- // "If parent is an HTML element:"
- if (isHtmlElement(parent_)) {
- // "If child is "a", and parent or some ancestor of parent is an a,
- // return false."
- //
- // "If child is a prohibited paragraph child name and parent or some
- // ancestor of parent is a p, return false."
- //
- // "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
- // some ancestor of parent is an HTML element with local name "h1",
- // "h2", "h3", "h4", "h5", or "h6", return false."
- var ancestor = parent_;
- while (ancestor) {
- if (child == "a" && isHtmlElement(ancestor, "a")) {
- return false;
- }
- if (prohibitedParagraphChildNames.indexOf(child) != -1
- && isHtmlElement(ancestor, "p")) {
- return false;
- }
- if (/^h[1-6]$/.test(child)
- && isHtmlElement(ancestor)
- && /^H[1-6]$/.test(ancestor.tagName)) {
- return false;
- }
- ancestor = ancestor.parentNode;
- }
-
- // "Let parent be the local name of parent."
- parent_ = parent_.tagName.toLowerCase();
- }
-
- // "If parent is an Element or DocumentFragment, return true."
- if (typeof parent_ == "object"
- && (parent_.nodeType == Node.ELEMENT_NODE
- || parent_.nodeType == Node.DOCUMENT_FRAGMENT_NODE)) {
- return true;
- }
-
- // "If parent is not a string, return false."
- if (typeof parent_ != "string") {
- return false;
- }
-
- // "If parent is in the following table, then return true if child is
- // listed as an allowed child, and false otherwise."
- switch (parent_) {
- case "colgroup":
- return child == "col";
- case "table":
- return ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1;
- case "tbody":
- case "thead":
- case "tfoot":
- return ["td", "th", "tr"].indexOf(child) != -1;
- case "tr":
- return ["td", "th"].indexOf(child) != -1;
- }
-
- // "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
- // "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
- // false."
- if (["body", "caption", "col", "colgroup", "frame", "frameset", "head",
- "html", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1) {
- return false;
- }
-
- // "If child is "dd" or "dt" and parent is not "dl", return false."
- if (["dd", "dt"].indexOf(child) != -1
- && parent_ != "dl") {
- return false;
- }
-
- // "If child is "li" and parent is not "ol" or "ul", return false."
- if (child == "li"
- && parent_ != "ol"
- && parent_ != "ul") {
- return false;
- }
-
- // "If parent is in the following table and child is listed as a prohibited
- // child, return false."
- var table = [
- [["a"], ["a"]],
- [["dd", "dt"], ["dd", "dt"]],
- [["h1", "h2", "h3", "h4", "h5", "h6"], ["h1", "h2", "h3", "h4", "h5", "h6"]],
- [["li"], ["li"]],
- [["nobr"], ["nobr"]],
- [["p"], prohibitedParagraphChildNames],
- [["td", "th"], ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]],
- ];
- for (var i = 0; i < table.length; i++) {
- if (table[i][0].indexOf(parent_) != -1
- && table[i][1].indexOf(child) != -1) {
- return false;
- }
- }
-
- // "Return true."
- return true;
-}
-
-function hasEditableDescendants(node) {
- for (var i = 0; i < node.childNodes.length; i++) {
- if (isEditable(node.childNodes[i])
- || hasEditableDescendants(node.childNodes[i])) {
- return true;
- }
- }
- return false;
-}
+//@}
+
+
+//////////////////////////////////////
+///// Inline formatting commands /////
+//////////////////////////////////////
+
+///// Inline formatting command definitions /////
+//@{
/**
* "A Node is effectively contained in a Range if either it is contained in the
@@ -1558,9 +1676,173 @@
].indexOf(node.tagName.toLowerCase()) != -1;
}
-/**
- * "effective value" per edit command spec
- */
+// "A modifiable element is a b, em, i, s, span, strong, sub, sup, or u element
+// with no attributes except possibly style; or a font element with no
+// attributes except possibly style, color, face, and/or size; or an a element
+// with no attributes except possibly style and/or href."
+function isModifiableElement(node) {
+ if (!isHtmlElement(node)) {
+ return false;
+ }
+
+ if (["B", "EM", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) != -1) {
+ if (node.attributes.length == 0) {
+ return true;
+ }
+
+ if (node.attributes.length == 1
+ && node.hasAttribute("style")) {
+ return true;
+ }
+ }
+
+ if (node.tagName == "FONT" || node.tagName == "A") {
+ var numAttrs = node.attributes.length;
+
+ if (node.hasAttribute("style")) {
+ numAttrs--;
+ }
+
+ if (node.tagName == "FONT") {
+ if (node.hasAttribute("color")) {
+ numAttrs--;
+ }
+
+ if (node.hasAttribute("face")) {
+ numAttrs--;
+ }
+
+ if (node.hasAttribute("size")) {
+ numAttrs--;
+ }
+ }
+
+ if (node.tagName == "A"
+ && node.hasAttribute("href")) {
+ numAttrs--;
+ }
+
+ if (numAttrs == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function isSimpleModifiableElement(node) {
+ // "A simple modifiable element is an HTML element for which at least one
+ // of the following holds:"
+ if (!isHtmlElement(node)) {
+ return false;
+ }
+
+ // Only these elements can possibly be a simple modifiable element.
+ if (["A", "B", "EM", "FONT", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) == -1) {
+ return false;
+ }
+
+ // "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
+ // element with no attributes."
+ if (node.attributes.length == 0) {
+ return true;
+ }
+
+ // If it's got more than one attribute, everything after this fails.
+ if (node.attributes.length > 1) {
+ return false;
+ }
+
+ // "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
+ // element with exactly one attribute, which is style, which sets no CSS
+ // properties (including invalid or unrecognized properties)."
+ //
+ // Not gonna try for invalid or unrecognized.
+ if (node.hasAttribute("style")
+ && node.style.length == 0) {
+ return true;
+ }
+
+ // "It is an a element with exactly one attribute, which is href."
+ if (node.tagName == "A"
+ && node.hasAttribute("href")) {
+ return true;
+ }
+
+ // "It is a font element with exactly one attribute, which is either color,
+ // face, or size."
+ if (node.tagName == "FONT"
+ && (node.hasAttribute("color")
+ || node.hasAttribute("face")
+ || node.hasAttribute("size")
+ )) {
+ return true;
+ }
+
+ // "It is a b or strong element with exactly one attribute, which is style,
+ // and the style attribute sets exactly one CSS property (including invalid
+ // or unrecognized properties), which is "font-weight"."
+ if ((node.tagName == "B" || node.tagName == "STRONG")
+ && node.hasAttribute("style")
+ && node.style.length == 1
+ && node.style.fontWeight != "") {
+ return true;
+ }
+
+ // "It is an i or em element with exactly one attribute, which is style,
+ // and the style attribute sets exactly one CSS property (including invalid
+ // or unrecognized properties), which is "font-style"."
+ if ((node.tagName == "I" || node.tagName == "EM")
+ && node.hasAttribute("style")
+ && node.style.length == 1
+ && node.style.fontStyle != "") {
+ return true;
+ }
+
+ // "It is a sub or sub element with exactly one attribute, which is style,
+ // and the style attribute sets exactly one CSS property (including invalid
+ // or unrecognized properties), which is "vertical-align"."
+ if ((node.tagName == "SUB" || node.tagName == "SUP")
+ && node.hasAttribute("style")
+ && node.style.length == 1
+ && node.style.verticalAlign != "") {
+ return true;
+ }
+
+ // "It is an a, font, or span element with exactly one attribute, which is
+ // style, and the style attribute sets exactly one CSS property (including
+ // invalid or unrecognized properties), and that property is not
+ // "text-decoration"."
+ if ((node.tagName == "A" || node.tagName == "FONT" || node.tagName == "SPAN")
+ && node.hasAttribute("style")
+ && node.style.length == 1
+ && node.style.textDecoration == "") {
+ return true;
+ }
+
+ // "It is an a, font, s, span, strike, or u element with exactly one
+ // attribute, which is style, and the style attribute sets exactly one CSS
+ // property (including invalid or unrecognized properties), which is
+ // "text-decoration", which is set to "line-through" or "underline" or
+ // "overline" or "none"."
+ if (["A", "FONT", "S", "SPAN", "STRIKE", "U"].indexOf(node.tagName) != -1
+ && node.hasAttribute("style")
+ && node.style.length == 1
+ && (node.style.textDecoration == "line-through"
+ || node.style.textDecoration == "underline"
+ || node.style.textDecoration == "overline"
+ || node.style.textDecoration == "none")) {
+ return true;
+ }
+
+ return false;
+}
+
+//@}
+
+///// Assorted inline formatting command definitions /////
+//@{
+
function getEffectiveValue(node, command) {
// "If neither node nor its parent is an Element, return null."
if (node.nodeType != Node.ELEMENT_NODE
@@ -1705,9 +1987,6 @@
return getComputedStyle(node)[getRelevantCssProperty(command)];
}
-/**
- * "specified value" per edit command spec
- */
function getSpecifiedValue(element, command) {
// "If command is "hiliteColor" and element's display property does not
// compute to "inline", return null."
@@ -1907,242 +2186,10 @@
movePreservingRanges(node, candidate, -1);
}
-// "A modifiable element is a b, em, i, s, span, strong, sub, sup, or u element
-// with no attributes except possibly style; or a font element with no
-// attributes except possibly style, color, face, and/or size; or an a element
-// with no attributes except possibly style and/or href."
-function isModifiableElement(node) {
- if (!isHtmlElement(node)) {
- return false;
- }
-
- if (["B", "EM", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) != -1) {
- if (node.attributes.length == 0) {
- return true;
- }
-
- if (node.attributes.length == 1
- && node.hasAttribute("style")) {
- return true;
- }
- }
-
- if (node.tagName == "FONT" || node.tagName == "A") {
- var numAttrs = node.attributes.length;
-
- if (node.hasAttribute("style")) {
- numAttrs--;
- }
-
- if (node.tagName == "FONT") {
- if (node.hasAttribute("color")) {
- numAttrs--;
- }
-
- if (node.hasAttribute("face")) {
- numAttrs--;
- }
-
- if (node.hasAttribute("size")) {
- numAttrs--;
- }
- }
-
- if (node.tagName == "A"
- && node.hasAttribute("href")) {
- numAttrs--;
- }
-
- if (numAttrs == 0) {
- return true;
- }
- }
-
- return false;
-}
-
-function isSimpleModifiableElement(node) {
- // "A simple modifiable element is an HTML element for which at least one
- // of the following holds:"
- if (!isHtmlElement(node)) {
- return false;
- }
-
- // Only these elements can possibly be a simple modifiable element.
- if (["A", "B", "EM", "FONT", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) == -1) {
- return false;
- }
-
- // "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
- // element with no attributes."
- if (node.attributes.length == 0) {
- return true;
- }
-
- // If it's got more than one attribute, everything after this fails.
- if (node.attributes.length > 1) {
- return false;
- }
-
- // "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
- // element with exactly one attribute, which is style, which sets no CSS
- // properties (including invalid or unrecognized properties)."
- //
- // Not gonna try for invalid or unrecognized.
- if (node.hasAttribute("style")
- && node.style.length == 0) {
- return true;
- }
-
- // "It is an a element with exactly one attribute, which is href."
- if (node.tagName == "A"
- && node.hasAttribute("href")) {
- return true;
- }
-
- // "It is a font element with exactly one attribute, which is either color,
- // face, or size."
- if (node.tagName == "FONT"
- && (node.hasAttribute("color")
- || node.hasAttribute("face")
- || node.hasAttribute("size")
- )) {
- return true;
- }
-
- // "It is a b or strong element with exactly one attribute, which is style,
- // and the style attribute sets exactly one CSS property (including invalid
- // or unrecognized properties), which is "font-weight"."
- if ((node.tagName == "B" || node.tagName == "STRONG")
- && node.hasAttribute("style")
- && node.style.length == 1
- && node.style.fontWeight != "") {
- return true;
- }
-
- // "It is an i or em element with exactly one attribute, which is style,
- // and the style attribute sets exactly one CSS property (including invalid
- // or unrecognized properties), which is "font-style"."
- if ((node.tagName == "I" || node.tagName == "EM")
- && node.hasAttribute("style")
- && node.style.length == 1
- && node.style.fontStyle != "") {
- return true;
- }
-
- // "It is a sub or sub element with exactly one attribute, which is style,
- // and the style attribute sets exactly one CSS property (including invalid
- // or unrecognized properties), which is "vertical-align"."
- if ((node.tagName == "SUB" || node.tagName == "SUP")
- && node.hasAttribute("style")
- && node.style.length == 1
- && node.style.verticalAlign != "") {
- return true;
- }
-
- // "It is an a, font, or span element with exactly one attribute, which is
- // style, and the style attribute sets exactly one CSS property (including
- // invalid or unrecognized properties), and that property is not
- // "text-decoration"."
- if ((node.tagName == "A" || node.tagName == "FONT" || node.tagName == "SPAN")
- && node.hasAttribute("style")
- && node.style.length == 1
- && node.style.textDecoration == "") {
- return true;
- }
-
- // "It is an a, font, s, span, strike, or u element with exactly one
- // attribute, which is style, and the style attribute sets exactly one CSS
- // property (including invalid or unrecognized properties), which is
- // "text-decoration", which is set to "line-through" or "underline" or
- // "overline" or "none"."
- if (["A", "FONT", "S", "SPAN", "STRIKE", "U"].indexOf(node.tagName) != -1
- && node.hasAttribute("style")
- && node.style.length == 1
- && (node.style.textDecoration == "line-through"
- || node.style.textDecoration == "underline"
- || node.style.textDecoration == "overline"
- || node.style.textDecoration == "none")) {
- return true;
- }
-
- return false;
-}
-
-function movePreservingRanges(node, newParent, newIndex) {
- // For convenience, I allow newIndex to be -1 to mean "insert at the end".
- if (newIndex == -1) {
- newIndex = newParent.childNodes.length;
- }
-
- // "When the user agent is to move a Node to a new location, preserving
- // ranges, it must remove the Node from its original parent (if any), then
- // insert it in the new location. In doing so, however, it must ignore the
- // regular range mutation rules, and instead follow these rules:"
-
- // "Let node be the moved Node, old parent and old index be the old parent
- // (which may be null) and index, and new parent and new index be the new
- // parent and index."
- var oldParent = node.parentNode;
- var oldIndex = getNodeIndex(node);
-
- // We only even attempt to preserve the global range object, not every
- // range out there (the latter is probably impossible).
- var start = [globalRange.startContainer, globalRange.startOffset];
- var end = [globalRange.endContainer, globalRange.endOffset];
-
- // "If a boundary point's node is the same as or a descendant of node,
- // leave it unchanged, so it moves to the new location."
- //
- // No modifications necessary.
-
- // "If a boundary point's node is new parent and its offset is greater than
- // new index, add one to its offset."
- if (globalRange.startContainer == newParent
- && globalRange.startOffset > newIndex) {
- start[1]++;
- }
- if (globalRange.endContainer == newParent
- && globalRange.endOffset > newIndex) {
- end[1]++;
- }
-
- // "If a boundary point's node is old parent and its offset is old index or
- // old index + 1, set its node to new parent and add new index − old index
- // to its offset."
- if (globalRange.startContainer == oldParent
- && (globalRange.startOffset == oldIndex
- || globalRange.startOffset == oldIndex + 1)) {
- start[0] = newParent;
- start[1] += newIndex - oldIndex;
- }
- if (globalRange.endContainer == oldParent
- && (globalRange.endOffset == oldIndex
- || globalRange.endOffset == oldIndex + 1)) {
- end[0] = newParent;
- end[1] += newIndex - oldIndex;
- }
-
- // "If a boundary point's node is old parent and its offset is greater than
- // old index + 1, subtract one from its offset."
- if (globalRange.startContainer == oldParent
- && globalRange.startOffset > oldIndex + 1) {
- start[1]--;
- }
- if (globalRange.endContainer == oldParent
- && globalRange.endOffset > oldIndex + 1) {
- end[1]--;
- }
-
- // Now actually move it and preserve the range.
- if (newParent.childNodes.length == newIndex) {
- newParent.appendChild(node);
- } else {
- newParent.insertBefore(node, newParent.childNodes[newIndex]);
- }
- globalRange.setStart(start[0], start[1]);
- globalRange.setEnd(end[0], end[1]);
-}
+//@}
+
+///// Decomposing a range into nodes /////
+//@{
function decomposeRange(range) {
// "If range's start and end are the same, return an empty list."
@@ -2216,312 +2263,10 @@
return ret;
}
-function normalizeSublists(item) {
- // "If item is not an li or it is not editable or its parent is not
- // editable, abort these steps."
- if (!isHtmlElement(item, "LI")
- || !isEditable(item)
- || !isEditable(item.parentNode)) {
- return;
- }
-
- // "Let new item be null."
- var newItem = null;
-
- // "While item has an ol or ul child:"
- while ([].some.call(item.childNodes, function (node) { return isHtmlElement(node, ["OL", "UL"]) })) {
- // "Let child be the last child of item."
- var child = item.lastChild;
-
- // "If child is an ol or ul, or new item is null and child is a Text
- // node whose data consists of zero of more space characters:"
- if (isHtmlElement(child, ["OL", "UL"])
- || (!newItem && child.nodeType == Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {
- // "Set new item to null."
- newItem = null;
-
- // "Insert child into the parent of item immediately following
- // item, preserving ranges."
- movePreservingRanges(child, item.parentNode, 1 + getNodeIndex(item));
-
- // "Otherwise:"
- } else {
- // "If new item is null, let new item be the result of calling
- // createElement("li") on the ownerDocument of item, then insert
- // new item into the parent of item immediately after item."
- if (!newItem) {
- newItem = item.ownerDocument.createElement("li");
- item.parentNode.insertBefore(newItem, item.nextSibling);
- }
-
- // "Insert child into new item as its first child, preserving
- // ranges."
- movePreservingRanges(child, newItem, 0);
- }
- }
-}
-
-function blockExtendRange(range) {
- // "Let start node, start offset, end node, and end offset be the start
- // and end nodes and offsets of the range."
- var startNode = range.startContainer;
- var startOffset = range.startOffset;
- var endNode = range.endContainer;
- var endOffset = range.endOffset;
-
- // "If some ancestor container of start node is an li, set start offset to
- // the index of the last such li in tree order, and set start node to that
- // li's parent."
- for (
- var ancestorContainer = startNode;
- ancestorContainer;
- ancestorContainer = ancestorContainer.parentNode
- ) {
- if (isHtmlElement(ancestorContainer, "LI")) {
- startOffset = getNodeIndex(ancestorContainer);
- startNode = ancestorContainer.parentNode;
- break;
- }
- }
-
- // "Repeat the following steps:"
- while (true) {
- // "If start node is a Text or Comment node or start offset is 0,
- // set start offset to the index of start node and then set start
- // node to its parent."
- if (startNode.nodeType == Node.TEXT_NODE
- || startNode.nodeType == Node.COMMENT_NODE
- || startOffset == 0) {
- startOffset = getNodeIndex(startNode);
- startNode = startNode.parentNode;
-
- // "Otherwise, if start offset is start node's length and start node's
- // last child is an inline node that's not a br, subtract one from
- // start offset."
- } else if (startOffset == getNodeLength(startNode)
- && isInlineNode(startNode.lastChild)
- && !isHtmlElement(startNode.lastChild, "br")) {
- startOffset--;
-
- // "Otherwise, if start node has a child with index start offset, and
- // that child and its previousSibling are both inline nodes and the
- // previousSibling isn't a br, subtract one from start offset."
- } else if (startOffset < startNode.childNodes.length
- && isInlineNode(startNode.childNodes[startOffset])
- && isInlineNode(startNode.childNodes[startOffset].previousSibling)
- && !isHtmlElement(startNode.childNodes[startOffset].previousSibling, "BR")) {
- startOffset--;
-
- // "Otherwise, break from this loop."
- } else {
- break;
- }
- }
-
- // "If some ancestor container of end node is an li, set end offset to one
- // plus the index of the last such li in tree order, and set end node to
- // that li's parent."
- for (
- var ancestorContainer = endNode;
- ancestorContainer;
- ancestorContainer = ancestorContainer.parentNode
- ) {
- if (isHtmlElement(ancestorContainer, "LI")) {
- endOffset = 1 + getNodeIndex(ancestorContainer);
- endNode = ancestorContainer.parentNode;
- break;
- }
- }
-
- // "Repeat the following steps:"
- while (true) {
- // "If end node is a Text or Comment node or end offset is equal to the
- // length of end node, set end offset to one plus the index of end node
- // and then set end node to its parent."
- if (endNode.nodeType == Node.TEXT_NODE
- || endNode.nodeType == Node.COMMENT_NODE
- || endOffset == getNodeLength(endNode)) {
- endOffset = 1 + getNodeIndex(endNode);
- endNode = endNode.parentNode;
-
- // "Otherwise, if end offset is 0 and end node's first child is an
- // inline node that's not a br, add one to end offset."
- } else if (endOffset == 0
- && isInlineNode(endNode.firstChild)
- && !isHtmlElement(endNode.firstChild, "br")) {
- endOffset++;
-
- // "Otherwise, if end node has a child with index end offset, and that
- // child and its previousSibling are both inline nodes, and the
- // previousSibling isn't a br, add one to end offset."
- } else if (endOffset < endNode.childNodes.length
- && isInlineNode(endNode.childNodes[endOffset])
- && isInlineNode(endNode.childNodes[endOffset].previousSibling)
- && !isHtmlElement(endNode.childNodes[endOffset], "BR")) {
- endOffset++;
-
- // "Otherwise, break from this loop."
- } else {
- break;
- }
- }
-
- // "If the child of end node with index end offset is a br, add one to end
- // offset."
- if (isHtmlElement(endNode.childNodes[endOffset], "BR")) {
- endOffset++;
- }
-
- // "While end offset is equal to the length of end node, set end offset to
- // one plus the index of end node and then set end node to its parent."
- while (endOffset == getNodeLength(endNode)) {
- endOffset = 1 + getNodeIndex(endNode);
- endNode = endNode.parentNode;
- }
-
- // "Let new range be a new range whose start and end nodes and offsets
- // are start node, start offset, end node, and end offset."
- var newRange = startNode.ownerDocument.createRange();
- newRange.setStart(startNode, startOffset);
- newRange.setEnd(endNode, endOffset);
-
- // "Return new range."
- return newRange;
-}
-
-function blockFormat(inputNodes, value) {
- // "For each node in input nodes, while either node is a descendant of an
- // editable HTML element in the same editing host with local name
- // "address", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre"; or node's
- // parent is not null, and "p" is not an allowed child of node's parent:
- // split the parent of the one-node list consisting of node."
- for (var i = 0; i < inputNodes.length; i++) {
- var node = inputNodes[i];
-
- while (true) {
- if (node.parentNode
- && !isAllowedChild("p", node.parentNode)) {
- splitParent([node]);
- continue;
- }
-
- var ancestor = node.parentNode;
- while (ancestor
- && !isHtmlElement(ancestor, ["ADDRESS", "H1", "H2", "H3", "H4", "H5", "H6", "P", "PRE"])) {
- ancestor = ancestor.parentNode;
- }
- if (ancestor
- && isEditable(ancestor)
- && inSameEditingHost(node, ancestor)) {
- splitParent([node]);
- } else {
- break;
- }
- }
- }
-
- // "Let node list be a list of nodes, initially empty."
- var nodeList = [];
-
- // "For each node in input nodes, fix prohibited paragraph descendants of
- // node, and append the resulting nodes to node list."
- for (var i = 0; i < inputNodes.length; i++) {
- nodeList = nodeList.concat(fixProhibitedParagraphDescendants(inputNodes[i]));
- }
-
- // "If value is "div" or "p", then while node list is not empty:"
- if (value == "div" || value == "p") {
- while (nodeList.length) {
- // "If the first member of node list is a non-list single-line
- // container, set the tag name of the first member of node list
- // to value, then remove the first member from node list and
- // continue this loop from the beginning."
- if (isNonListSingleLineContainer(nodeList[0])) {
- setTagName(nodeList[0], value);
- nodeList.shift();
- continue;
- }
-
- // "Let sublist be an empty list of nodes."
- var sublist = [];
-
- // "Remove the first member of node list and append it to
- // sublist."
- sublist.push(nodeList.shift());
-
- // "While node list is not empty, and the first member of node
- // list is the nextSibling of the last member of sublist, and
- // the first member of node list is not a non-list single-line
- // container, and the last member of sublist is not a br,
- // remove the first member of node list and append it to
- // sublist."
- while (nodeList.length
- && nodeList[0] == sublist[sublist.length - 1].nextSibling
- && !isNonListSingleLineContainer(nodeList[0])
- && !isHtmlElement(sublist[sublist.length - 1], "BR")) {
- sublist.push(nodeList.shift());
- }
-
- // "Wrap sublist, with sibling criteria matching nothing and
- // new parent instructions returning the result of running
- // createElement(value) on the context object."
- wrap(sublist,
- function() { return false },
- function() { return document.createElement(value) });
- }
-
- // "Otherwise, while node list is not empty:"
- } else {
- while (nodeList.length) {
- var sublist;
-
- // "If the first member of node list is a non-list single-line
- // container:"
- if (isNonListSingleLineContainer(nodeList[0])) {
- // "Let sublist be the children of the first member of node
- // list."
- sublist = [].slice.call(nodeList[0].childNodes);
-
- // "Remove the first member of node list from its parent,
- // preserving its descendants."
- removePreservingDescendants(nodeList[0]);
-
- // "Remove the first member from node list."
- nodeList.shift();
-
- // "Otherwise:"
- } else {
- // "Let sublist be an empty list of nodes."
- sublist = [];
-
- // "Remove the first member of node list and append it to
- // sublist."
- sublist.push(nodeList.shift());
-
- // "While node list is not empty, and the first member of
- // node list is the nextSibling of the last member of
- // sublist, and the first member of node list is not a
- // non-list single-line container, and the last member of
- // sublist is not a br, remove the first member of node
- // list and append it to sublist."
- while (nodeList.length
- && nodeList[0] == sublist[sublist.length - 1].nextSibling
- && !isNonListSingleLineContainer(nodeList[0])
- && !isHtmlElement(sublist[sublist.length - 1], "BR")) {
- sublist.push(nodeList.shift());
- }
- }
-
- // "Wrap sublist, with sibling criteria matching any HTML
- // element with local name value and no attributes, and new
- // parent instructions returning the result of running
- // createElement(value) on the context object."
- wrap(sublist,
- function(node) { return isHtmlElement(node, value.toUpperCase()) && !node.attributes.length },
- function() { return document.createElement(value) });
- }
- }
-}
+//@}
+
+///// Clearing an element's value /////
+//@{
function clearValue(element, command) {
// "If element's specified value for command is null, return the empty
@@ -2642,6 +2387,11 @@
return [newElement];
}
+//@}
+
+///// Pushing down values /////
+//@{
+
function pushDownValues(node, command, newValue) {
// "If node's parent is not an Element, abort this algorithm."
if (!node.parentNode
@@ -2746,6 +2496,11 @@
}
}
+//@}
+
+///// Forcing the value of a node /////
+//@{
+
function forceValue(node, command, newValue) {
// "If node's parent is null, abort this algorithm."
if (!node.parentNode) {
@@ -3070,6 +2825,11 @@
}
}
+//@}
+
+///// Setting the value of a node /////
+//@{
+
function setNodeValue(node, command, newValue) {
// "If node is not editable:"
if (!isEditable(node)) {
@@ -3117,8 +2877,1119 @@
}
}
-// This is bad :(
-var globalRange = null;
+//@}
+
+
+/////////////////////////////////////
+///// Block formatting commands /////
+/////////////////////////////////////
+
+///// Block formatting command definitions /////
+//@{
+
+// "A potential indentation element is either a blockquote, or a div that has a
+// style attribute that sets "margin" or some subproperty of it."
+function isPotentialIndentationElement(node) {
+ if (!isHtmlElement(node)) {
+ return false;
+ }
+
+ if (node.tagName == "BLOCKQUOTE") {
+ return true;
+ }
+
+ if (node.tagName != "DIV") {
+ return false;
+ }
+
+ for (var i = 0; i < node.style.length; i++) {
+ // Approximate check
+ if (/^(-[a-z]+-)?margin/.test(node.style[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// "An indentation element is a potential indentation element that has no
+// attributes other than one or more of
+//
+// * "a style attribute that sets no properties other than "margin", "border",
+// "padding", or subproperties of those;
+// * "a class attribute;
+// * "a dir attribute."
+function isIndentationElement(node) {
+ if (!isPotentialIndentationElement(node)) {
+ return false;
+ }
+
+ if (node.tagName != "BLOCKQUOTE" && node.tagName != "DIV") {
+ return false;
+ }
+
+ for (var i = 0; i < node.attributes.length; i++) {
+ if (!isHtmlNamespace(node.attributes[i].namespaceURI)
+ || ["style", "class", "dir"].indexOf(node.attributes[i].name) == -1) {
+ return false;
+ }
+ }
+
+ for (var i = 0; i < node.style.length; i++) {
+ // This is approximate, but it works well enough for my purposes.
+ if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// "A non-list single-line container is an HTML element with local name
+// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre"."
+function isNonListSingleLineContainer(node) {
+ return isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5",
+ "h6", "p", "pre"]);
+}
+
+// "A single-line container is either a non-list single-line container, or an
+// HTML element with local name "li", "dt", or "dd"."
+function isSingleLineContainer(node) {
+ return isNonListSingleLineContainer(node)
+ || isHtmlElement(node, ["li", "dt", "dd"]);
+}
+
+// "The default single-line container name is "p"."
+var defaultSingleLineContainerName = "p";
+
+//@}
+
+///// Assorted block formatting algorithm commands /////
+//@{
+
+function fixDisallowedAncestors(node) {
+ // "If node is an li and its parent is not an ol, unset its value
+ // attribute, if set."
+ if (isHtmlElement(node, "li")
+ && !isHtmlElement(node.parentNode, "ol")) {
+ node.removeAttribute("value");
+ }
+
+ // "If node is an li and its parent is not an ol or ul, or node is a dt or
+ // dd and its parent is not a dl:"
+ if ((isHtmlElement(node, "li")
+ && !isHtmlElement(node.parentNode, ["ol", "ul"]))
+ || (isHtmlElement(node, ["dt", "dd"])
+ && !isHtmlElement(node.parentNode, "dl"))) {
+ // "Set the tag name of node to the default single-line container name,
+ // 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);
+
+ // "Abort these steps."
+ return;
+ }
+
+ // "If node is an allowed child of its parent, or it is not an allowed
+ // child of any of its ancestors in the same editing host, abort these
+ // steps and do nothing."
+ if (isAllowedChild(node, node.parentNode)) {
+ return;
+ }
+ var ancestor = node.parentNode;
+ var hasAllowedAncestor = false;
+ while (inSameEditingHost(node, ancestor)) {
+ if (isAllowedChild(node, ancestor)) {
+ hasAllowedAncestor = true;
+ break;
+ }
+ ancestor = ancestor.parentNode;
+ }
+ if (!hasAllowedAncestor) {
+ return;
+ }
+
+ // "While node is not an allowed child of its parent, split the parent of
+ // the one-node list consisting of node."
+ while (!isAllowedChild(node, node.parentNode)) {
+ splitParent([node]);
+ }
+}
+
+function fixProhibitedParagraphDescendants(node) {
+ // "If node has no children, return the one-node list consisting of node."
+ if (!node.hasChildNodes()) {
+ return [node];
+ }
+
+ // "Let children be the children of node."
+ var children = [].slice.call(node.childNodes);
+
+ // "Fix prohibited paragraph descendants of each member of children."
+ for (var i = 0; i < children.length; i++) {
+ fixProhibitedParagraphDescendants(children[i]);
+ }
+
+ // "Let children be the children of node."
+ children = [].slice.call(node.childNodes);
+
+ // "For each child in children, if child is a prohibited paragraph child,
+ // split the parent of the one-node list consisting of child."
+ for (var i = 0; i < children.length; i++) {
+ if (isProhibitedParagraphChild(children[i])) {
+ splitParent([children[i]]);
+ }
+ }
+
+ // "If node's parent is null, let node equal the last member of children."
+ if (!node.parentNode) {
+ node = children[children.length - 1];
+ }
+
+ // "Let node list be a list of nodes, initially empty."
+ var nodeList = [];
+
+ // "Repeat these steps:"
+ while (true) {
+ // "Prepend node to node list."
+ nodeList.unshift(node);
+
+ // "If node is children's first member, or children's first member's
+ // parent, break from this loop."
+ if (node == children[0]
+ || node == children[0].parentNode) {
+ break;
+ }
+
+ // "Set node to its previousSibling."
+ node = node.previousSibling;
+ }
+
+ // "Return node list."
+ return nodeList;
+}
+
+function indentNodes(nodeList) {
+ // "If node list is empty, do nothing and abort these steps."
+ if (!nodeList.length) {
+ return;
+ }
+
+ // "Let first node be the first member of node list."
+ var firstNode = nodeList[0];
+
+ // "If first node's parent is an ol or ul:"
+ if (isHtmlElement(firstNode.parentNode, ["OL", "UL"])) {
+ // "Let tag be the local name of the parent of first node."
+ var tag = firstNode.parentNode.tagName;
+
+ // "Wrap node list, with sibling criteria matching only HTML elements
+ // with local name tag and new parent instructions returning the result
+ // of calling createElement(tag) on the ownerDocument of first node."
+ wrap(nodeList,
+ function(node) { return isHtmlElement(node, tag) },
+ function() { return firstNode.ownerDocument.createElement(tag) });
+
+ // "Abort these steps."
+ return;
+ }
+
+ // "Wrap node list, with sibling criteria matching any indentation element,
+ // and new parent instructions to return the result of calling
+ // createElement("blockquote") on the ownerDocument of first node. Let new
+ // parent be the result."
+ var newParent = wrap(nodeList,
+ function(node) { return isIndentationElement(node) },
+ function() { return firstNode.ownerDocument.createElement("blockquote") });
+
+ // "Fix disallowed ancestors of new parent."
+ fixDisallowedAncestors(newParent);
+}
+
+function normalizeSublists(item) {
+ // "If item is not an li or it is not editable or its parent is not
+ // editable, abort these steps."
+ if (!isHtmlElement(item, "LI")
+ || !isEditable(item)
+ || !isEditable(item.parentNode)) {
+ return;
+ }
+
+ // "Let new item be null."
+ var newItem = null;
+
+ // "While item has an ol or ul child:"
+ while ([].some.call(item.childNodes, function (node) { return isHtmlElement(node, ["OL", "UL"]) })) {
+ // "Let child be the last child of item."
+ var child = item.lastChild;
+
+ // "If child is an ol or ul, or new item is null and child is a Text
+ // node whose data consists of zero of more space characters:"
+ if (isHtmlElement(child, ["OL", "UL"])
+ || (!newItem && child.nodeType == Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {
+ // "Set new item to null."
+ newItem = null;
+
+ // "Insert child into the parent of item immediately following
+ // item, preserving ranges."
+ movePreservingRanges(child, item.parentNode, 1 + getNodeIndex(item));
+
+ // "Otherwise:"
+ } else {
+ // "If new item is null, let new item be the result of calling
+ // createElement("li") on the ownerDocument of item, then insert
+ // new item into the parent of item immediately after item."
+ if (!newItem) {
+ newItem = item.ownerDocument.createElement("li");
+ item.parentNode.insertBefore(newItem, item.nextSibling);
+ }
+
+ // "Insert child into new item as its first child, preserving
+ // ranges."
+ movePreservingRanges(child, newItem, 0);
+ }
+ }
+}
+
+//@}
+
+///// Block-extending a range /////
+//@{
+
+function blockExtendRange(range) {
+ // "Let start node, start offset, end node, and end offset be the start
+ // and end nodes and offsets of the range."
+ var startNode = range.startContainer;
+ var startOffset = range.startOffset;
+ var endNode = range.endContainer;
+ var endOffset = range.endOffset;
+
+ // "If some ancestor container of start node is an li, set start offset to
+ // the index of the last such li in tree order, and set start node to that
+ // li's parent."
+ for (
+ var ancestorContainer = startNode;
+ ancestorContainer;
+ ancestorContainer = ancestorContainer.parentNode
+ ) {
+ if (isHtmlElement(ancestorContainer, "LI")) {
+ startOffset = getNodeIndex(ancestorContainer);
+ startNode = ancestorContainer.parentNode;
+ break;
+ }
+ }
+
+ // "Repeat the following steps:"
+ while (true) {
+ // "If start node is a Text or Comment node or start offset is 0,
+ // set start offset to the index of start node and then set start
+ // node to its parent."
+ if (startNode.nodeType == Node.TEXT_NODE
+ || startNode.nodeType == Node.COMMENT_NODE
+ || startOffset == 0) {
+ startOffset = getNodeIndex(startNode);
+ startNode = startNode.parentNode;
+
+ // "Otherwise, if start offset is start node's length and start node's
+ // last child is an inline node that's not a br, subtract one from
+ // start offset."
+ } else if (startOffset == getNodeLength(startNode)
+ && isInlineNode(startNode.lastChild)
+ && !isHtmlElement(startNode.lastChild, "br")) {
+ startOffset--;
+
+ // "Otherwise, if start node has a child with index start offset, and
+ // that child and its previousSibling are both inline nodes and the
+ // previousSibling isn't a br, subtract one from start offset."
+ } else if (startOffset < startNode.childNodes.length
+ && isInlineNode(startNode.childNodes[startOffset])
+ && isInlineNode(startNode.childNodes[startOffset].previousSibling)
+ && !isHtmlElement(startNode.childNodes[startOffset].previousSibling, "BR")) {
+ startOffset--;
+
+ // "Otherwise, break from this loop."
+ } else {
+ break;
+ }
+ }
+
+ // "If some ancestor container of end node is an li, set end offset to one
+ // plus the index of the last such li in tree order, and set end node to
+ // that li's parent."
+ for (
+ var ancestorContainer = endNode;
+ ancestorContainer;
+ ancestorContainer = ancestorContainer.parentNode
+ ) {
+ if (isHtmlElement(ancestorContainer, "LI")) {
+ endOffset = 1 + getNodeIndex(ancestorContainer);
+ endNode = ancestorContainer.parentNode;
+ break;
+ }
+ }
+
+ // "Repeat the following steps:"
+ while (true) {
+ // "If end node is a Text or Comment node or end offset is equal to the
+ // length of end node, set end offset to one plus the index of end node
+ // and then set end node to its parent."
+ if (endNode.nodeType == Node.TEXT_NODE
+ || endNode.nodeType == Node.COMMENT_NODE
+ || endOffset == getNodeLength(endNode)) {
+ endOffset = 1 + getNodeIndex(endNode);
+ endNode = endNode.parentNode;
+
+ // "Otherwise, if end offset is 0 and end node's first child is an
+ // inline node that's not a br, add one to end offset."
+ } else if (endOffset == 0
+ && isInlineNode(endNode.firstChild)
+ && !isHtmlElement(endNode.firstChild, "br")) {
+ endOffset++;
+
+ // "Otherwise, if end node has a child with index end offset, and that
+ // child and its previousSibling are both inline nodes, and the
+ // previousSibling isn't a br, add one to end offset."
+ } else if (endOffset < endNode.childNodes.length
+ && isInlineNode(endNode.childNodes[endOffset])
+ && isInlineNode(endNode.childNodes[endOffset].previousSibling)
+ && !isHtmlElement(endNode.childNodes[endOffset], "BR")) {
+ endOffset++;
+
+ // "Otherwise, break from this loop."
+ } else {
+ break;
+ }
+ }
+
+ // "If the child of end node with index end offset is a br, add one to end
+ // offset."
+ if (isHtmlElement(endNode.childNodes[endOffset], "BR")) {
+ endOffset++;
+ }
+
+ // "While end offset is equal to the length of end node, set end offset to
+ // one plus the index of end node and then set end node to its parent."
+ while (endOffset == getNodeLength(endNode)) {
+ endOffset = 1 + getNodeIndex(endNode);
+ endNode = endNode.parentNode;
+ }
+
+ // "Let new range be a new range whose start and end nodes and offsets
+ // are start node, start offset, end node, and end offset."
+ var newRange = startNode.ownerDocument.createRange();
+ newRange.setStart(startNode, startOffset);
+ newRange.setEnd(endNode, endOffset);
+
+ // "Return new range."
+ return newRange;
+}
+
+//@}
+
+///// Block-formatting a node list /////
+//@{
+
+function blockFormat(inputNodes, value) {
+ // "For each node in input nodes, while either node is a descendant of an
+ // editable HTML element in the same editing host with local name
+ // "address", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre"; or node's
+ // parent is not null, and "p" is not an allowed child of node's parent:
+ // split the parent of the one-node list consisting of node."
+ for (var i = 0; i < inputNodes.length; i++) {
+ var node = inputNodes[i];
+
+ while (true) {
+ if (node.parentNode
+ && !isAllowedChild("p", node.parentNode)) {
+ splitParent([node]);
+ continue;
+ }
+
+ var ancestor = node.parentNode;
+ while (ancestor
+ && !isHtmlElement(ancestor, ["ADDRESS", "H1", "H2", "H3", "H4", "H5", "H6", "P", "PRE"])) {
+ ancestor = ancestor.parentNode;
+ }
+ if (ancestor
+ && isEditable(ancestor)
+ && inSameEditingHost(node, ancestor)) {
+ splitParent([node]);
+ } else {
+ break;
+ }
+ }
+ }
+
+ // "Let node list be a list of nodes, initially empty."
+ var nodeList = [];
+
+ // "For each node in input nodes, fix prohibited paragraph descendants of
+ // node, and append the resulting nodes to node list."
+ for (var i = 0; i < inputNodes.length; i++) {
+ nodeList = nodeList.concat(fixProhibitedParagraphDescendants(inputNodes[i]));
+ }
+
+ // "If value is "div" or "p", then while node list is not empty:"
+ if (value == "div" || value == "p") {
+ while (nodeList.length) {
+ // "If the first member of node list is a non-list single-line
+ // container, set the tag name of the first member of node list
+ // to value, then remove the first member from node list and
+ // continue this loop from the beginning."
+ if (isNonListSingleLineContainer(nodeList[0])) {
+ setTagName(nodeList[0], value);
+ nodeList.shift();
+ continue;
+ }
+
+ // "Let sublist be an empty list of nodes."
+ var sublist = [];
+
+ // "Remove the first member of node list and append it to
+ // sublist."
+ sublist.push(nodeList.shift());
+
+ // "While node list is not empty, and the first member of node
+ // list is the nextSibling of the last member of sublist, and
+ // the first member of node list is not a non-list single-line
+ // container, and the last member of sublist is not a br,
+ // remove the first member of node list and append it to
+ // sublist."
+ while (nodeList.length
+ && nodeList[0] == sublist[sublist.length - 1].nextSibling
+ && !isNonListSingleLineContainer(nodeList[0])
+ && !isHtmlElement(sublist[sublist.length - 1], "BR")) {
+ sublist.push(nodeList.shift());
+ }
+
+ // "Wrap sublist, with sibling criteria matching nothing and
+ // new parent instructions returning the result of running
+ // createElement(value) on the context object."
+ wrap(sublist,
+ function() { return false },
+ function() { return document.createElement(value) });
+ }
+
+ // "Otherwise, while node list is not empty:"
+ } else {
+ while (nodeList.length) {
+ var sublist;
+
+ // "If the first member of node list is a non-list single-line
+ // container:"
+ if (isNonListSingleLineContainer(nodeList[0])) {
+ // "Let sublist be the children of the first member of node
+ // list."
+ sublist = [].slice.call(nodeList[0].childNodes);
+
+ // "Remove the first member of node list from its parent,
+ // preserving its descendants."
+ removePreservingDescendants(nodeList[0]);
+
+ // "Remove the first member from node list."
+ nodeList.shift();
+
+ // "Otherwise:"
+ } else {
+ // "Let sublist be an empty list of nodes."
+ sublist = [];
+
+ // "Remove the first member of node list and append it to
+ // sublist."
+ sublist.push(nodeList.shift());
+
+ // "While node list is not empty, and the first member of
+ // node list is the nextSibling of the last member of
+ // sublist, and the first member of node list is not a
+ // non-list single-line container, and the last member of
+ // sublist is not a br, remove the first member of node
+ // list and append it to sublist."
+ while (nodeList.length
+ && nodeList[0] == sublist[sublist.length - 1].nextSibling
+ && !isNonListSingleLineContainer(nodeList[0])
+ && !isHtmlElement(sublist[sublist.length - 1], "BR")) {
+ sublist.push(nodeList.shift());
+ }
+ }
+
+ // "Wrap sublist, with sibling criteria matching any HTML
+ // element with local name value and no attributes, and new
+ // parent instructions returning the result of running
+ // createElement(value) on the context object."
+ wrap(sublist,
+ function(node) { return isHtmlElement(node, value.toUpperCase()) && !node.attributes.length },
+ function() { return document.createElement(value) });
+ }
+ }
+}
+
+//@}
+
+///// Outdenting a node /////
+//@{
+
+function outdentNode(node) {
+ // "If node is not editable, abort these steps."
+ if (!isEditable(node)) {
+ return;
+ }
+
+ // "If node is an indentation element, remove node, preserving its
+ // descendants. Then abort these steps."
+ if (isIndentationElement(node)) {
+ removePreservingDescendants(node);
+ return;
+ }
+
+ // "If node is a potential indentation element:"
+ if (isPotentialIndentationElement(node)) {
+ // "Unset the class and dir attributes of node, if any."
+ node.removeAttribute("class");
+ node.removeAttribute("dir");
+
+ // "Unset the margin, padding, and border CSS properties of node."
+ node.style.margin = "";
+ node.style.padding = "";
+ node.style.border = "";
+ if (node.getAttribute("style") == "") {
+ node.removeAttribute("style");
+ }
+
+ // "Set the tag name of node to "div"."
+ setTagName(node, "div");
+
+ // "Abort these steps."
+ return;
+ }
+
+ // "Let current ancestor be node's parent."
+ var currentAncestor = node.parentNode;
+
+ // "Let ancestor list be a list of nodes, initially empty."
+ var ancestorList = [];
+
+ // "While current ancestor is an editable Element that is not an
+ // indentation element, append current ancestor to ancestor list and then
+ // set current ancestor to its parent."
+ while (isEditable(currentAncestor)
+ && currentAncestor.nodeType == Node.ELEMENT_NODE
+ && !isIndentationElement(currentAncestor)) {
+ ancestorList.push(currentAncestor);
+ currentAncestor = currentAncestor.parentNode;
+ }
+
+ // "If current ancestor is not an editable indentation element:"
+ if (!isEditable(currentAncestor)
+ || !isIndentationElement(currentAncestor)) {
+ // "Let current ancestor be node's parent."
+ currentAncestor = node.parentNode;
+
+ // "Let ancestor list be the empty list."
+ ancestorList = [];
+
+ // "While current ancestor is an editable Element that is not a
+ // potential indentation element, append current ancestor to ancestor
+ // list and then set current ancestor to its parent."
+ while (isEditable(currentAncestor)
+ && currentAncestor.nodeType == Node.ELEMENT_NODE
+ && !isPotentialIndentationElement(currentAncestor)) {
+ ancestorList.push(currentAncestor);
+ currentAncestor = currentAncestor.parentNode;
+ }
+ }
+
+ // "If node is an ol or ul, and either current ancestor is not an editable
+ // potential indentation element or node's parent is an ol or ul:"
+ if (isHtmlElement(node, ["OL", "UL"])
+ && (!isEditable(currentAncestor)
+ || !isPotentialIndentationElement(currentAncestor)
+ || isHtmlElement(node.parentNode, ["OL", "UL"]))) {
+ // "Unset the reversed, start, and type attributes of node, if any are
+ // set."
+ node.removeAttribute("reversed");
+ node.removeAttribute("start");
+ node.removeAttribute("type");
+
+ // "Let children be the children of node."
+ var children = [].slice.call(node.childNodes);
+
+ // "If node has attributes, and its parent or not an ol or ul, set the
+ // tag name of node to "div"."
+ if (node.attributes.length
+ && !isHtmlElement(node.parentNode, ["OL", "UL"])) {
+ setTagName(node, "div");
+
+ // "Otherwise remove node, preserving its descendants."
+ } else {
+ removePreservingDescendants(node);
+ }
+
+ // "Fix disallowed ancestors of each member of children."
+ for (var i = 0; i < children.length; i++) {
+ fixDisallowedAncestors(children[i]);
+ }
+
+ // "Abort these steps."
+ return;
+ }
+
+ // "If current ancestor is not an editable potential indentation element,
+ // abort these steps."
+ if (!isEditable(currentAncestor)
+ || !isPotentialIndentationElement(currentAncestor)) {
+ return;
+ }
+
+ // "Append current ancestor to ancestor list."
+ ancestorList.push(currentAncestor);
+
+ // "Let original ancestor be current ancestor."
+ var originalAncestor = currentAncestor;
+
+ // "While ancestor list is not empty:"
+ while (ancestorList.length) {
+ // "Let current ancestor be the last member of ancestor list."
+ //
+ // "Remove the last member of ancestor list."
+ currentAncestor = ancestorList.pop();
+
+ // "Let target be the child of current ancestor that is equal to either
+ // node or the last member of ancestor list."
+ var target = node.parentNode == currentAncestor
+ ? node
+ : ancestorList[ancestorList.length - 1];
+
+ // "If target is an inline node that is not a br, and its nextSibling
+ // is a br, remove target's nextSibling from its parent."
+ if (isInlineNode(target)
+ && !isHtmlElement(target, "BR")
+ && isHtmlElement(target.nextSibling, "BR")) {
+ target.parentNode.removeChild(target.nextSibling);
+ }
+
+ // "Let preceding siblings be the preceding siblings of target, and let
+ // following siblings be the following siblings of target."
+ var precedingSiblings = [].slice.call(currentAncestor.childNodes, 0, getNodeIndex(target));
+ var followingSiblings = [].slice.call(currentAncestor.childNodes, 1 + getNodeIndex(target));
+
+ // "Indent preceding siblings."
+ indentNodes(precedingSiblings);
+
+ // "Indent following siblings."
+ indentNodes(followingSiblings);
+ }
+
+ // "Outdent original ancestor."
+ outdentNode(originalAncestor);
+}
+
+//@}
+
+///// Toggling lists /////
+//@{
+
+function toggleLists(range, tagName) {
+ tagName = tagName.toUpperCase();
+
+ // "Let other tag name be "ol" if tag name is "ul", and "ul" if tag name is
+ // "ol"."
+ var otherTagName = tagName == "OL" ? "UL" : "OL";
+
+ // "Let items be a list of all lis that are ancestor containers of the
+ // range's start and/or end node."
+ //
+ // Has to be in tree order, remember!
+ var items = [];
+ for (var node = range.endContainer; node != range.commonAncestorContainer; node = node.parentNode) {
+ if (isHtmlElement(node, "LI")) {
+ items.unshift(node);
+ }
+ }
+ for (var node = range.startContainer; node != range.commonAncestorContainer; node = node.parentNode) {
+ if (isHtmlElement(node, "LI")) {
+ items.unshift(node);
+ }
+ }
+ for (var node = range.commonAncestorContainer; node; node = node.parentNode) {
+ if (isHtmlElement(node, "LI")) {
+ items.unshift(node);
+ }
+ }
+
+ // "For each item in items, normalize sublists of item."
+ for (var i = 0; i < items.length; i++) {
+ normalizeSublists(items[i]);
+ }
+
+ // "Block-extend the range, and let new range be the result."
+ var newRange = blockExtendRange(range);
+
+ // "Let node list be a list of nodes, initially empty."
+ var nodeList = [];
+
+ // "For each node node contained in new range, if node is editable; the
+ // last member of node list (if any) is not an ancestor of node; node
+ // is not a potential indentation element; and either node is an ol or
+ // ul, or its parent is an ol or ul, or it is an allowed child of "li";
+ // then append node to node list."
+ for (
+ var node = newRange.startContainer;
+ node != nextNodeDescendants(newRange.endContainer);
+ node = nextNode(node)
+ ) {
+ if (isEditable(node)
+ && isContained(node, newRange)
+ && (!nodeList.length || !isAncestor(nodeList[nodeList.length - 1], node))
+ && !isPotentialIndentationElement(node)
+ && (isHtmlElement(node, ["OL", "UL"])
+ || isHtmlElement(node.parentNode, ["OL", "UL"])
+ || isAllowedChild(node, "li"))) {
+ nodeList.push(node);
+ }
+ }
+
+ // "If every member of node list is equal to or the child of an HTML
+ // element with local name tag name, and no member of node list is equal to
+ // or the ancestor of an HTML element with local name other tag name, then
+ // while node list is not empty:"
+ if (nodeList.every(function(node) { return isHtmlElement(node, tagName) || isHtmlElement(node.parentNode, tagName) })
+ && !nodeList.some(function(node) { return isHtmlElement(node, otherTagName) || node.querySelector(otherTagName) })) {
+ while (nodeList.length) {
+ // "Let sublist be an empty list of nodes."
+ var sublist = [];
+
+ // "Remove the first member from node list and append it to
+ // sublist."
+ sublist.push(nodeList.shift());
+
+ // "If the first member of sublist is an HTML element with local
+ // name tag name, outdent it and continue this loop from the
+ // beginning."
+ if (isHtmlElement(sublist[0], tagName)) {
+ outdentNode(sublist[0]);
+ continue;
+ }
+
+ // "While node list is not empty, and the first member of node list
+ // is the nextSibling of the last member of sublist and is not an
+ // HTML element with local name tag name, remove the first member
+ // from node list and append it to sublist."
+ while (nodeList.length
+ && nodeList[0] == sublist[sublist.length - 1].nextSibling
+ && !isHtmlElement(nodeList[0], tagName)) {
+ sublist.push(nodeList.shift());
+ }
+
+ // "Split the parent of sublist."
+ splitParent(sublist);
+
+ // "Fix disallowed ancestors of each member of sublist."
+ for (var i = 0; i < sublist.length; i++) {
+ fixDisallowedAncestors(sublist[i]);
+ }
+ }
+
+ // "Otherwise, while node list is not empty:"
+ } else {
+ while (nodeList.length) {
+ // "Let sublist be an empty list of nodes."
+ var sublist = [];
+
+ // "Remove the first member from node list and append it to
+ // sublist."
+ sublist.push(nodeList.shift());
+
+ // "While node list is not empty, and the first member of node
+ // list is the nextSibling of the last member of sublist, and
+ // the last member of sublist and first member of node list are
+ // both inline nodes, and the last member of sublist is not a
+ // br, remove the first member from node list and append it to
+ // sublist."
+ while (nodeList.length
+ && nodeList[0] == sublist[sublist.length - 1].nextSibling
+ && isInlineNode(sublist[sublist.length - 1])
+ && isInlineNode(nodeList[0])
+ && !isHtmlElement(sublist[sublist.length - 1], "BR")) {
+ sublist.push(nodeList.shift());
+ }
+
+ // "If sublist contains more than one member, wrap sublist, with
+ // sibling criteria matching nothing and new parent instructions
+ // returning the result of calling createElement("li") on the
+ // context object. Let node be the result."
+ var node;
+ if (sublist.length > 1) {
+ node = wrap(sublist,
+ function() { return false },
+ function() { return document.createElement("li") });
+
+ // "Otherwise, let node be the sole member of sublist."
+ } else {
+ node = sublist[0];
+ }
+
+ // "If node is an HTML element with local name other tag name:"
+ if (isHtmlElement(node, otherTagName)) {
+ // "Let children be the children of node."
+ var children = [].slice.call(node.childNodes);
+
+ // "Remove node, preserving its descendants."
+ removePreservingDescendants(node);
+
+ // "Wrap children, with sibling criteria matching any HTML
+ // element with local name tag name and new parent instructions
+ // returning the result of calling createElement(tag name) on
+ // the context object. Let node be the result."
+ node = wrap(children,
+ function(node) { return isHtmlElement(node, tagName) },
+ function() { return document.createElement(tagName) });
+
+ // "Prepend the descendants of node that are HTML elements with
+ // local name other tag name (if any) to node list."
+ nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
+
+ // "Continue from the beginning of this loop."
+ continue;
+ }
+
+ // "If node is a p or div, set the tag name of node to "li",
+ // and let node be the result."
+ if (isHtmlElement(node, ["P", "DIV"])) {
+ node = setTagName(node, "li");
+ }
+
+ // "If node is the child of an HTML element with local name other
+ // tag name:"
+ if (isHtmlElement(node.parentNode, otherTagName)) {
+ // "Split the parent of the one-node list consisting of
+ // node."
+ splitParent([node]);
+
+ // "Wrap the one-node list consisting of node, with sibling
+ // criteria matching any HTML element with local name tag name,
+ // and with new parent instructions returning the result of
+ // calling createElement(tag name) on the context object."
+ wrap([node],
+ function(node) { return isHtmlElement(node, tagName) },
+ function() { return document.createElement(tagName) });
+
+ // "Prepend the descendants of node that are HTML elements with
+ // local name other tag name (if any) to node list."
+ nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
+
+ // "Continue from the beginning of this loop."
+ continue;
+ }
+
+ // "If node is equal to or the child of an HTML element with local
+ // name tag name, prepend the descendants of node that are HTML
+ // elements with local name other tag name (if any) to node list
+ // and continue from the beginning of this loop."
+ if (isHtmlElement(node, tagName)
+ || isHtmlElement(node.parentNode, tagName)) {
+ nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
+ continue;
+ }
+
+ // "If node is not an li, wrap the one-node list consisting of
+ // node, with sibling criteria matching nothing and new parent
+ // instructions returning the result of calling createElement("li")
+ // on the context object. Set node to the result."
+ if (!isHtmlElement(node, "LI")) {
+ node = wrap([node],
+ function() { return false },
+ function() { return document.createElement("li") });
+ }
+
+ // "Wrap the one-node list consisting of node, with the sibling
+ // criteria matching any HTML element with local name tag name, and
+ // the new parent instructions being the following:"
+ var newParent = wrap([node],
+ function(node) { return isHtmlElement(node, tagName) },
+ function() {
+ // "If the parent of node is not an editable indentation
+ // element, or the previousSibling of the parent of node is
+ // not an editable HTML element with local name tag name,
+ // call createElement(tag name) on the context object and
+ // return the result. Otherwise:"
+ if (!isEditable(node.parentNode)
+ || !isIndentationElement(node.parentNode)
+ || !isEditable(node.parentNode.previousSibling)
+ || !isHtmlElement(node.parentNode.previousSibling, tagName)) {
+ return document.createElement(tagName);
+ }
+
+ // "Let list be the previousSibling of the parent of node."
+ var list = node.parentNode.previousSibling;
+
+ // "Normalize sublists of list's last child."
+ normalizeSublists(list.lastChild);
+
+ // "If list's last child is not an editable HTML element
+ // with local name tag name, call createElement(tag name)
+ // on the context object, and append the result as the last
+ // child of list."
+ if (!isEditable(list.lastChild)
+ || !isHtmlElement(list.lastChild, tagName)) {
+ list.appendChild(document.createElement(tagName));
+ }
+
+ // "Return the last child of list."
+ return list.lastChild;
+ });
+
+ // "Fix disallowed ancestors of the previous step's result."
+ fixDisallowedAncestors(newParent);
+ }
+ }
+}
+
+//@}
+
+///// Justifying the selection /////
+//@{
+
+function justifySelection(alignment) {
+ // "Block-extend the active range, and let new range be the result."
+ var newRange = blockExtendRange(globalRange);
+
+ // "Let element list be a list of all editable Elements contained in new
+ // range that either has an attribute in the HTML namespace whose local
+ // name is "align", or has a style attribute that sets "text-align", or is
+ // a center."
+ var elementList = collectAllContainedNodes(newRange, function(node) {
+ return node.nodeType == Node.ELEMENT_NODE
+ && isEditable(node)
+ // Ignoring namespaces here
+ && (
+ node.hasAttribute("align")
+ || node.style.textAlign != ""
+ || isHtmlElement(node, "center")
+ );
+ });
+
+ // "For each element in element list:"
+ for (var i = 0; i < elementList.length; i++) {
+ var element = elementList[i];
+
+ // "If element has an attribute in the HTML namespace whose local name
+ // is "align", remove that attribute."
+ element.removeAttribute("align");
+
+ // "Unset the CSS property "text-align" on element, if it's set by a
+ // style attribute."
+ element.style.textAlign = "";
+ if (element.getAttribute("style") == "") {
+ element.removeAttribute("style");
+ }
+
+ // "If element is a div or center with no attributes, remove it,
+ // preserving its descendants."
+ if (isHtmlElement(element, ["div", "center"])
+ && !element.attributes.length) {
+ removePreservingDescendants(element);
+ }
+
+ // "If element is a center with one or more attributes, set the tag
+ // name of element to "div"."
+ if (isHtmlElement(element, "center")
+ && element.attributes.length) {
+ setTagName(element, "div");
+ }
+ }
+
+ // "Block-extend the active range, and let new range be the result."
+ newRange = blockExtendRange(globalRange);
+
+ // "Let node list be a list of nodes, initially empty."
+ var nodeList = [];
+
+ // "For each node node contained in new range, append node to node list if
+ // the last member of node list (if any) is not an ancestor of node; node
+ // is editable; and either node is an Element and the CSS property
+ // "text-align" does not compute to alignment on it, or it is not an
+ // Element, but its parent is an Element, and the CSS property "text-align"
+ // does not compute to alignment on its parent."
+ nodeList = collectContainedNodes(newRange, function(node) {
+ if (!isEditable(node)) {
+ return false;
+ }
+ // Gecko and WebKit have lots of fun here confusing us with
+ // vendor-specific values, and in Gecko's case "start".
+ var element = node.nodeType == Node.ELEMENT_NODE
+ ? node
+ : node.parentNode;
+ if (!element || element.nodeType != Node.ELEMENT_NODE) {
+ return false;
+ }
+ var computedAlign = getComputedStyle(element).textAlign
+ .replace(/^-(moz|webkit)-/, "");
+ if (computedAlign == "auto" || computedAlign == "start") {
+ // Depends on directionality. Note: this is a serious hack.
+ do {
+ var dir = element.dir.toLowerCase();
+ element = element.parentNode;
+ } while (element && element.nodeType == Node.ELEMENT_NODE && dir != "ltr" && dir != "rtl");
+ if (dir == "rtl") {
+ computedAlign = "right";
+ } else {
+ computedAlign = "left";
+ }
+ }
+ return computedAlign != alignment;
+ });
+
+ // "While node list is not empty:"
+ while (nodeList.length) {
+ // "Let sublist be a list of nodes, initially empty."
+ var sublist = [];
+
+ // "Remove the first member of node list and append it to sublist."
+ sublist.push(nodeList.shift());
+
+ // "While node list is not empty, and the first member of node list is
+ // the nextSibling of the last member of sublist, remove the first
+ // member of node list and append it to sublist."
+ while (nodeList.length
+ && nodeList[0] == sublist[sublist.length - 1].nextSibling) {
+ sublist.push(nodeList.shift());
+ }
+
+ // "Wrap sublist. Sibling criteria match any div that has one or both
+ // of the following two attributes, and no other attributes:
+ //
+ // * "An align attribute whose value is an ASCII case-insensitive
+ // match for alignment.
+ // * "A style attribute which sets exactly one CSS property
+ // (including unrecognized or invalid attributes), which is
+ // "text-align", which is set to alignment.
+ //
+ // "New parent instructions are to call createElement("div") on the
+ // context object, then set its CSS property "text-align" to alignment,
+ // and return the result."
+ wrap(sublist,
+ function(node) {
+ return isHtmlElement(node, "div")
+ && [].every.call(node.attributes, function(attr) {
+ return (attr.name == "align" && attr.value.toLowerCase() == alignment)
+ || (attr.name == "style" && node.style.length == 1 && node.style.textAlign == alignment);
+ });
+ },
+ function() {
+ var newParent = document.createElement("div");
+ newParent.setAttribute("style", "text-align: " + alignment);
+ return newParent;
+ }
+ );
+ }
+}
+
+//@}
function getRelevantCssProperty(command) {
var prop = {
@@ -4396,768 +5267,6 @@
}
}
-function fixDisallowedAncestors(node) {
- // "If node is an li and its parent is not an ol, unset its value
- // attribute, if set."
- if (isHtmlElement(node, "li")
- && !isHtmlElement(node.parentNode, "ol")) {
- node.removeAttribute("value");
- }
-
- // "If node is an li and its parent is not an ol or ul, or node is a dt or
- // dd and its parent is not a dl:"
- if ((isHtmlElement(node, "li")
- && !isHtmlElement(node.parentNode, ["ol", "ul"]))
- || (isHtmlElement(node, ["dt", "dd"])
- && !isHtmlElement(node.parentNode, "dl"))) {
- // "Set the tag name of node to the default single-line container name,
- // 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);
-
- // "Abort these steps."
- return;
- }
-
- // "If node is an allowed child of its parent, or it is not an allowed
- // child of any of its ancestors in the same editing host, abort these
- // steps and do nothing."
- if (isAllowedChild(node, node.parentNode)) {
- return;
- }
- var ancestor = node.parentNode;
- var hasAllowedAncestor = false;
- while (inSameEditingHost(node, ancestor)) {
- if (isAllowedChild(node, ancestor)) {
- hasAllowedAncestor = true;
- break;
- }
- ancestor = ancestor.parentNode;
- }
- if (!hasAllowedAncestor) {
- return;
- }
-
- // "While node is not an allowed child of its parent, split the parent of
- // the one-node list consisting of node."
- while (!isAllowedChild(node, node.parentNode)) {
- splitParent([node]);
- }
-}
-
-function fixProhibitedParagraphDescendants(node) {
- // "If node has no children, return the one-node list consisting of node."
- if (!node.hasChildNodes()) {
- return [node];
- }
-
- // "Let children be the children of node."
- var children = [].slice.call(node.childNodes);
-
- // "Fix prohibited paragraph descendants of each member of children."
- for (var i = 0; i < children.length; i++) {
- fixProhibitedParagraphDescendants(children[i]);
- }
-
- // "Let children be the children of node."
- children = [].slice.call(node.childNodes);
-
- // "For each child in children, if child is a prohibited paragraph child,
- // split the parent of the one-node list consisting of child."
- for (var i = 0; i < children.length; i++) {
- if (isProhibitedParagraphChild(children[i])) {
- splitParent([children[i]]);
- }
- }
-
- // "If node's parent is null, let node equal the last member of children."
- if (!node.parentNode) {
- node = children[children.length - 1];
- }
-
- // "Let node list be a list of nodes, initially empty."
- var nodeList = [];
-
- // "Repeat these steps:"
- while (true) {
- // "Prepend node to node list."
- nodeList.unshift(node);
-
- // "If node is children's first member, or children's first member's
- // parent, break from this loop."
- if (node == children[0]
- || node == children[0].parentNode) {
- break;
- }
-
- // "Set node to its previousSibling."
- node = node.previousSibling;
- }
-
- // "Return node list."
- return nodeList;
-}
-
-function indentNodes(nodeList) {
- // "If node list is empty, do nothing and abort these steps."
- if (!nodeList.length) {
- return;
- }
-
- // "Let first node be the first member of node list."
- var firstNode = nodeList[0];
-
- // "If first node's parent is an ol or ul:"
- if (isHtmlElement(firstNode.parentNode, ["OL", "UL"])) {
- // "Let tag be the local name of the parent of first node."
- var tag = firstNode.parentNode.tagName;
-
- // "Wrap node list, with sibling criteria matching only HTML elements
- // with local name tag and new parent instructions returning the result
- // of calling createElement(tag) on the ownerDocument of first node."
- wrap(nodeList,
- function(node) { return isHtmlElement(node, tag) },
- function() { return firstNode.ownerDocument.createElement(tag) });
-
- // "Abort these steps."
- return;
- }
-
- // "Wrap node list, with sibling criteria matching any indentation element,
- // and new parent instructions to return the result of calling
- // createElement("blockquote") on the ownerDocument of first node. Let new
- // parent be the result."
- var newParent = wrap(nodeList,
- function(node) { return isIndentationElement(node) },
- function() { return firstNode.ownerDocument.createElement("blockquote") });
-
- // "Fix disallowed ancestors of new parent."
- fixDisallowedAncestors(newParent);
-}
-
-function outdentNode(node) {
- // "If node is not editable, abort these steps."
- if (!isEditable(node)) {
- return;
- }
-
- // "If node is an indentation element, remove node, preserving its
- // descendants. Then abort these steps."
- if (isIndentationElement(node)) {
- removePreservingDescendants(node);
- return;
- }
-
- // "If node is a potential indentation element:"
- if (isPotentialIndentationElement(node)) {
- // "Unset the class and dir attributes of node, if any."
- node.removeAttribute("class");
- node.removeAttribute("dir");
-
- // "Unset the margin, padding, and border CSS properties of node."
- node.style.margin = "";
- node.style.padding = "";
- node.style.border = "";
- if (node.getAttribute("style") == "") {
- node.removeAttribute("style");
- }
-
- // "Set the tag name of node to "div"."
- setTagName(node, "div");
-
- // "Abort these steps."
- return;
- }
-
- // "Let current ancestor be node's parent."
- var currentAncestor = node.parentNode;
-
- // "Let ancestor list be a list of nodes, initially empty."
- var ancestorList = [];
-
- // "While current ancestor is an editable Element that is not an
- // indentation element, append current ancestor to ancestor list and then
- // set current ancestor to its parent."
- while (isEditable(currentAncestor)
- && currentAncestor.nodeType == Node.ELEMENT_NODE
- && !isIndentationElement(currentAncestor)) {
- ancestorList.push(currentAncestor);
- currentAncestor = currentAncestor.parentNode;
- }
-
- // "If current ancestor is not an editable indentation element:"
- if (!isEditable(currentAncestor)
- || !isIndentationElement(currentAncestor)) {
- // "Let current ancestor be node's parent."
- currentAncestor = node.parentNode;
-
- // "Let ancestor list be the empty list."
- ancestorList = [];
-
- // "While current ancestor is an editable Element that is not a
- // potential indentation element, append current ancestor to ancestor
- // list and then set current ancestor to its parent."
- while (isEditable(currentAncestor)
- && currentAncestor.nodeType == Node.ELEMENT_NODE
- && !isPotentialIndentationElement(currentAncestor)) {
- ancestorList.push(currentAncestor);
- currentAncestor = currentAncestor.parentNode;
- }
- }
-
- // "If node is an ol or ul, and either current ancestor is not an editable
- // potential indentation element or node's parent is an ol or ul:"
- if (isHtmlElement(node, ["OL", "UL"])
- && (!isEditable(currentAncestor)
- || !isPotentialIndentationElement(currentAncestor)
- || isHtmlElement(node.parentNode, ["OL", "UL"]))) {
- // "Unset the reversed, start, and type attributes of node, if any are
- // set."
- node.removeAttribute("reversed");
- node.removeAttribute("start");
- node.removeAttribute("type");
-
- // "Let children be the children of node."
- var children = [].slice.call(node.childNodes);
-
- // "If node has attributes, and its parent or not an ol or ul, set the
- // tag name of node to "div"."
- if (node.attributes.length
- && !isHtmlElement(node.parentNode, ["OL", "UL"])) {
- setTagName(node, "div");
-
- // "Otherwise remove node, preserving its descendants."
- } else {
- removePreservingDescendants(node);
- }
-
- // "Fix disallowed ancestors of each member of children."
- for (var i = 0; i < children.length; i++) {
- fixDisallowedAncestors(children[i]);
- }
-
- // "Abort these steps."
- return;
- }
-
- // "If current ancestor is not an editable potential indentation element,
- // abort these steps."
- if (!isEditable(currentAncestor)
- || !isPotentialIndentationElement(currentAncestor)) {
- return;
- }
-
- // "Append current ancestor to ancestor list."
- ancestorList.push(currentAncestor);
-
- // "Let original ancestor be current ancestor."
- var originalAncestor = currentAncestor;
-
- // "While ancestor list is not empty:"
- while (ancestorList.length) {
- // "Let current ancestor be the last member of ancestor list."
- //
- // "Remove the last member of ancestor list."
- currentAncestor = ancestorList.pop();
-
- // "Let target be the child of current ancestor that is equal to either
- // node or the last member of ancestor list."
- var target = node.parentNode == currentAncestor
- ? node
- : ancestorList[ancestorList.length - 1];
-
- // "If target is an inline node that is not a br, and its nextSibling
- // is a br, remove target's nextSibling from its parent."
- if (isInlineNode(target)
- && !isHtmlElement(target, "BR")
- && isHtmlElement(target.nextSibling, "BR")) {
- target.parentNode.removeChild(target.nextSibling);
- }
-
- // "Let preceding siblings be the preceding siblings of target, and let
- // following siblings be the following siblings of target."
- var precedingSiblings = [].slice.call(currentAncestor.childNodes, 0, getNodeIndex(target));
- var followingSiblings = [].slice.call(currentAncestor.childNodes, 1 + getNodeIndex(target));
-
- // "Indent preceding siblings."
- indentNodes(precedingSiblings);
-
- // "Indent following siblings."
- indentNodes(followingSiblings);
- }
-
- // "Outdent original ancestor."
- outdentNode(originalAncestor);
-}
-
-function toggleLists(range, tagName) {
- tagName = tagName.toUpperCase();
-
- // "Let other tag name be "ol" if tag name is "ul", and "ul" if tag name is
- // "ol"."
- var otherTagName = tagName == "OL" ? "UL" : "OL";
-
- // "Let items be a list of all lis that are ancestor containers of the
- // range's start and/or end node."
- //
- // Has to be in tree order, remember!
- var items = [];
- for (var node = range.endContainer; node != range.commonAncestorContainer; node = node.parentNode) {
- if (isHtmlElement(node, "LI")) {
- items.unshift(node);
- }
- }
- for (var node = range.startContainer; node != range.commonAncestorContainer; node = node.parentNode) {
- if (isHtmlElement(node, "LI")) {
- items.unshift(node);
- }
- }
- for (var node = range.commonAncestorContainer; node; node = node.parentNode) {
- if (isHtmlElement(node, "LI")) {
- items.unshift(node);
- }
- }
-
- // "For each item in items, normalize sublists of item."
- for (var i = 0; i < items.length; i++) {
- normalizeSublists(items[i]);
- }
-
- // "Block-extend the range, and let new range be the result."
- var newRange = blockExtendRange(range);
-
- // "Let node list be a list of nodes, initially empty."
- var nodeList = [];
-
- // "For each node node contained in new range, if node is editable; the
- // last member of node list (if any) is not an ancestor of node; node
- // is not a potential indentation element; and either node is an ol or
- // ul, or its parent is an ol or ul, or it is an allowed child of "li";
- // then append node to node list."
- for (
- var node = newRange.startContainer;
- node != nextNodeDescendants(newRange.endContainer);
- node = nextNode(node)
- ) {
- if (isEditable(node)
- && isContained(node, newRange)
- && (!nodeList.length || !isAncestor(nodeList[nodeList.length - 1], node))
- && !isPotentialIndentationElement(node)
- && (isHtmlElement(node, ["OL", "UL"])
- || isHtmlElement(node.parentNode, ["OL", "UL"])
- || isAllowedChild(node, "li"))) {
- nodeList.push(node);
- }
- }
-
- // "If every member of node list is equal to or the child of an HTML
- // element with local name tag name, and no member of node list is equal to
- // or the ancestor of an HTML element with local name other tag name, then
- // while node list is not empty:"
- if (nodeList.every(function(node) { return isHtmlElement(node, tagName) || isHtmlElement(node.parentNode, tagName) })
- && !nodeList.some(function(node) { return isHtmlElement(node, otherTagName) || node.querySelector(otherTagName) })) {
- while (nodeList.length) {
- // "Let sublist be an empty list of nodes."
- var sublist = [];
-
- // "Remove the first member from node list and append it to
- // sublist."
- sublist.push(nodeList.shift());
-
- // "If the first member of sublist is an HTML element with local
- // name tag name, outdent it and continue this loop from the
- // beginning."
- if (isHtmlElement(sublist[0], tagName)) {
- outdentNode(sublist[0]);
- continue;
- }
-
- // "While node list is not empty, and the first member of node list
- // is the nextSibling of the last member of sublist and is not an
- // HTML element with local name tag name, remove the first member
- // from node list and append it to sublist."
- while (nodeList.length
- && nodeList[0] == sublist[sublist.length - 1].nextSibling
- && !isHtmlElement(nodeList[0], tagName)) {
- sublist.push(nodeList.shift());
- }
-
- // "Split the parent of sublist."
- splitParent(sublist);
-
- // "Fix disallowed ancestors of each member of sublist."
- for (var i = 0; i < sublist.length; i++) {
- fixDisallowedAncestors(sublist[i]);
- }
- }
-
- // "Otherwise, while node list is not empty:"
- } else {
- while (nodeList.length) {
- // "Let sublist be an empty list of nodes."
- var sublist = [];
-
- // "Remove the first member from node list and append it to
- // sublist."
- sublist.push(nodeList.shift());
-
- // "While node list is not empty, and the first member of node
- // list is the nextSibling of the last member of sublist, and
- // the last member of sublist and first member of node list are
- // both inline nodes, and the last member of sublist is not a
- // br, remove the first member from node list and append it to
- // sublist."
- while (nodeList.length
- && nodeList[0] == sublist[sublist.length - 1].nextSibling
- && isInlineNode(sublist[sublist.length - 1])
- && isInlineNode(nodeList[0])
- && !isHtmlElement(sublist[sublist.length - 1], "BR")) {
- sublist.push(nodeList.shift());
- }
-
- // "If sublist contains more than one member, wrap sublist, with
- // sibling criteria matching nothing and new parent instructions
- // returning the result of calling createElement("li") on the
- // context object. Let node be the result."
- var node;
- if (sublist.length > 1) {
- node = wrap(sublist,
- function() { return false },
- function() { return document.createElement("li") });
-
- // "Otherwise, let node be the sole member of sublist."
- } else {
- node = sublist[0];
- }
-
- // "If node is an HTML element with local name other tag name:"
- if (isHtmlElement(node, otherTagName)) {
- // "Let children be the children of node."
- var children = [].slice.call(node.childNodes);
-
- // "Remove node, preserving its descendants."
- removePreservingDescendants(node);
-
- // "Wrap children, with sibling criteria matching any HTML
- // element with local name tag name and new parent instructions
- // returning the result of calling createElement(tag name) on
- // the context object. Let node be the result."
- node = wrap(children,
- function(node) { return isHtmlElement(node, tagName) },
- function() { return document.createElement(tagName) });
-
- // "Prepend the descendants of node that are HTML elements with
- // local name other tag name (if any) to node list."
- nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
-
- // "Continue from the beginning of this loop."
- continue;
- }
-
- // "If node is a p or div, set the tag name of node to "li",
- // and let node be the result."
- if (isHtmlElement(node, ["P", "DIV"])) {
- node = setTagName(node, "li");
- }
-
- // "If node is the child of an HTML element with local name other
- // tag name:"
- if (isHtmlElement(node.parentNode, otherTagName)) {
- // "Split the parent of the one-node list consisting of
- // node."
- splitParent([node]);
-
- // "Wrap the one-node list consisting of node, with sibling
- // criteria matching any HTML element with local name tag name,
- // and with new parent instructions returning the result of
- // calling createElement(tag name) on the context object."
- wrap([node],
- function(node) { return isHtmlElement(node, tagName) },
- function() { return document.createElement(tagName) });
-
- // "Prepend the descendants of node that are HTML elements with
- // local name other tag name (if any) to node list."
- nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
-
- // "Continue from the beginning of this loop."
- continue;
- }
-
- // "If node is equal to or the child of an HTML element with local
- // name tag name, prepend the descendants of node that are HTML
- // elements with local name other tag name (if any) to node list
- // and continue from the beginning of this loop."
- if (isHtmlElement(node, tagName)
- || isHtmlElement(node.parentNode, tagName)) {
- nodeList = [].slice.call(node.querySelectorAll(otherTagName)).concat(nodeList);
- continue;
- }
-
- // "If node is not an li, wrap the one-node list consisting of
- // node, with sibling criteria matching nothing and new parent
- // instructions returning the result of calling createElement("li")
- // on the context object. Set node to the result."
- if (!isHtmlElement(node, "LI")) {
- node = wrap([node],
- function() { return false },
- function() { return document.createElement("li") });
- }
-
- // "Wrap the one-node list consisting of node, with the sibling
- // criteria matching any HTML element with local name tag name, and
- // the new parent instructions being the following:"
- var newParent = wrap([node],
- function(node) { return isHtmlElement(node, tagName) },
- function() {
- // "If the parent of node is not an editable indentation
- // element, or the previousSibling of the parent of node is
- // not an editable HTML element with local name tag name,
- // call createElement(tag name) on the context object and
- // return the result. Otherwise:"
- if (!isEditable(node.parentNode)
- || !isIndentationElement(node.parentNode)
- || !isEditable(node.parentNode.previousSibling)
- || !isHtmlElement(node.parentNode.previousSibling, tagName)) {
- return document.createElement(tagName);
- }
-
- // "Let list be the previousSibling of the parent of node."
- var list = node.parentNode.previousSibling;
-
- // "Normalize sublists of list's last child."
- normalizeSublists(list.lastChild);
-
- // "If list's last child is not an editable HTML element
- // with local name tag name, call createElement(tag name)
- // on the context object, and append the result as the last
- // child of list."
- if (!isEditable(list.lastChild)
- || !isHtmlElement(list.lastChild, tagName)) {
- list.appendChild(document.createElement(tagName));
- }
-
- // "Return the last child of list."
- return list.lastChild;
- });
-
- // "Fix disallowed ancestors of the previous step's result."
- fixDisallowedAncestors(newParent);
- }
- }
-}
-
-function justifySelection(alignment) {
- // "Block-extend the active range, and let new range be the result."
- var newRange = blockExtendRange(globalRange);
-
- // "Let element list be a list of all editable Elements contained in new
- // range that either has an attribute in the HTML namespace whose local
- // name is "align", or has a style attribute that sets "text-align", or is
- // a center."
- var elementList = collectAllContainedNodes(newRange, function(node) {
- return node.nodeType == Node.ELEMENT_NODE
- && isEditable(node)
- // Ignoring namespaces here
- && (
- node.hasAttribute("align")
- || node.style.textAlign != ""
- || isHtmlElement(node, "center")
- );
- });
-
- // "For each element in element list:"
- for (var i = 0; i < elementList.length; i++) {
- var element = elementList[i];
-
- // "If element has an attribute in the HTML namespace whose local name
- // is "align", remove that attribute."
- element.removeAttribute("align");
-
- // "Unset the CSS property "text-align" on element, if it's set by a
- // style attribute."
- element.style.textAlign = "";
- if (element.getAttribute("style") == "") {
- element.removeAttribute("style");
- }
-
- // "If element is a div or center with no attributes, remove it,
- // preserving its descendants."
- if (isHtmlElement(element, ["div", "center"])
- && !element.attributes.length) {
- removePreservingDescendants(element);
- }
-
- // "If element is a center with one or more attributes, set the tag
- // name of element to "div"."
- if (isHtmlElement(element, "center")
- && element.attributes.length) {
- setTagName(element, "div");
- }
- }
-
- // "Block-extend the active range, and let new range be the result."
- newRange = blockExtendRange(globalRange);
-
- // "Let node list be a list of nodes, initially empty."
- var nodeList = [];
-
- // "For each node node contained in new range, append node to node list if
- // the last member of node list (if any) is not an ancestor of node; node
- // is editable; and either node is an Element and the CSS property
- // "text-align" does not compute to alignment on it, or it is not an
- // Element, but its parent is an Element, and the CSS property "text-align"
- // does not compute to alignment on its parent."
- nodeList = collectContainedNodes(newRange, function(node) {
- if (!isEditable(node)) {
- return false;
- }
- // Gecko and WebKit have lots of fun here confusing us with
- // vendor-specific values, and in Gecko's case "start".
- var element = node.nodeType == Node.ELEMENT_NODE
- ? node
- : node.parentNode;
- if (!element || element.nodeType != Node.ELEMENT_NODE) {
- return false;
- }
- var computedAlign = getComputedStyle(element).textAlign
- .replace(/^-(moz|webkit)-/, "");
- if (computedAlign == "auto" || computedAlign == "start") {
- // Depends on directionality. Note: this is a serious hack.
- do {
- var dir = element.dir.toLowerCase();
- element = element.parentNode;
- } while (element && element.nodeType == Node.ELEMENT_NODE && dir != "ltr" && dir != "rtl");
- if (dir == "rtl") {
- computedAlign = "right";
- } else {
- computedAlign = "left";
- }
- }
- return computedAlign != alignment;
- });
-
- // "While node list is not empty:"
- while (nodeList.length) {
- // "Let sublist be a list of nodes, initially empty."
- var sublist = [];
-
- // "Remove the first member of node list and append it to sublist."
- sublist.push(nodeList.shift());
-
- // "While node list is not empty, and the first member of node list is
- // the nextSibling of the last member of sublist, remove the first
- // member of node list and append it to sublist."
- while (nodeList.length
- && nodeList[0] == sublist[sublist.length - 1].nextSibling) {
- sublist.push(nodeList.shift());
- }
-
- // "Wrap sublist. Sibling criteria match any div that has one or both
- // of the following two attributes, and no other attributes:
- //
- // * "An align attribute whose value is an ASCII case-insensitive
- // match for alignment.
- // * "A style attribute which sets exactly one CSS property
- // (including unrecognized or invalid attributes), which is
- // "text-align", which is set to alignment.
- //
- // "New parent instructions are to call createElement("div") on the
- // context object, then set its CSS property "text-align" to alignment,
- // and return the result."
- wrap(sublist,
- function(node) {
- return isHtmlElement(node, "div")
- && [].every.call(node.attributes, function(attr) {
- return (attr.name == "align" && attr.value.toLowerCase() == alignment)
- || (attr.name == "style" && node.style.length == 1 && node.style.textAlign == alignment);
- });
- },
- function() {
- var newParent = document.createElement("div");
- newParent.setAttribute("style", "text-align: " + alignment);
- return newParent;
- }
- );
- }
-}
-
-// "A potential indentation element is either a blockquote, or a div that has a
-// style attribute that sets "margin" or some subproperty of it."
-function isPotentialIndentationElement(node) {
- if (!isHtmlElement(node)) {
- return false;
- }
-
- if (node.tagName == "BLOCKQUOTE") {
- return true;
- }
-
- if (node.tagName != "DIV") {
- return false;
- }
-
- for (var i = 0; i < node.style.length; i++) {
- // Approximate check
- if (/^(-[a-z]+-)?margin/.test(node.style[i])) {
- return true;
- }
- }
-
- return false;
-}
-
-// "An indentation element is a potential indentation element that has no
-// attributes other than one or more of
-//
-// * "a style attribute that sets no properties other than "margin", "border",
-// "padding", or subproperties of those;
-// * "a class attribute;
-// * "a dir attribute."
-function isIndentationElement(node) {
- if (!isPotentialIndentationElement(node)) {
- return false;
- }
-
- if (node.tagName != "BLOCKQUOTE" && node.tagName != "DIV") {
- return false;
- }
-
- for (var i = 0; i < node.attributes.length; i++) {
- if (!isHtmlNamespace(node.attributes[i].namespaceURI)
- || ["style", "class", "dir"].indexOf(node.attributes[i].name) == -1) {
- return false;
- }
- }
-
- for (var i = 0; i < node.style.length; i++) {
- // This is approximate, but it works well enough for my purposes.
- if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {
- return false;
- }
- }
-
- return true;
-}
-
-// "A non-list single-line container is an HTML element with local name
-// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre"."
-function isNonListSingleLineContainer(node) {
- return isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5",
- "h6", "p", "pre"]);
-}
-
-// "A single-line container is either a non-list single-line container, or an
-// HTML element with local name "li", "dt", or "dd"."
-function isSingleLineContainer(node) {
- return isNonListSingleLineContainer(node)
- || isHtmlElement(node, ["li", "dt", "dd"]);
-}
-
function myQueryCommandState(command) {
command = command.toLowerCase();
@@ -5270,3 +5379,5 @@
return "";
}
+
+// vim: foldmarker=@{,@} foldmethod=marker
--- a/source.html Tue Jun 14 13:46:20 2011 -0600
+++ b/source.html Tue Jun 14 14:37:27 2011 -0600
@@ -355,6 +355,80 @@
things. If it is, it needs some adjustment, like to handle collapsed
whitespace nodes and collapsed br's.
+<!-- I don't remember why I wrote this. Keeping it around just in case it
+turns out to be useful.
+
+<p><var>node</var> is an <dfn>extraneous line break</dfn> if the following
+algorithm returns true:
+
+<p class=XXX>This is positively horrible. It attempts to move complicated CSS
+logic into DOM methods, and does so badly. Can we somehow make this less evil,
+preferably much less evil?
+
+<ol>
+ <li>If <var>node</var> is not a [[br]], return false.
+
+ <li>Let <var>ancestor block</var> be <var>node</var>.
+
+ <li>While <var>ancestor block</var> is an <span>inline node</span>, set
+ <var>ancestor block</var> to its [[parent]].
+
+ <li>Let <var>previous box</var> be <var>node</var>.
+
+ <li>While <var>previous box</var> is equal to or an [[ancestor]] of
+ <var>node</var>, set <var>previous box</var> to the [[node]] immediately
+ before it in [[treeorder]], or null if there is no such [[node]].
+
+ <li>Let <var>next box</var> be the [[node]] immediately after <var>node</var>
+ in [[treeorder]], or null if there is such [[node]].
+
+ <li>If <var>previous box</var> is null, or is not a [[descendant]] of
+ <var>ancestor block</var>, or is not an <span>inline node</span>, or is a
+ [[br]], return false.
+ <!- -
+ This means br either is the first thing in its block container that generates
+ an inline box, or it's the first thing after another block container, or the
+ first thing after a br. In any case, the line break will be visible.
+
+ Otherwise, it will be invisible as long as it immediately precedes a block
+ box boundary.
+ - ->
+
+ <li>If <var>ancestor block</var> is not null and <var>node</var> is the last
+ [[descendant]] of <var>ancestor block</var>, return true.
+ <!- - Precedes the end of ancestor block's box. - ->
+
+ <li>If <var>next box</var> is not null and not an <span>inline node</span>,
+ return true.
+ <!- - Precedes the start of next box's box. - ->
+
+ <li>Return false.
+</ol>
+-->
+
+<p>Each [[htmldocument]] has a boolean <dfn>CSS styling flag</dfn> associated
+with it, which must initially be false. (<span>The <code
+title>styleWithCSS</code> command</span> can be used to modify or query it, by
+means of the <code>execCommand()</code> and <code>queryCommandState()</code>
+methods.)
+
+<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,
+if the author has overridden the method with a custom method, the standard
+method must be run rather than the custom one.
+
+<p>When a list or set of [[nodes]] is assigned to a variable without specifying
+the order, they must be initially in [[treeorder]], if they share a root.
+(If they don't share a root, the order will be specified.) When the user agent
+is instructed to run particular steps for each member of a list, it must do so
+sequentially in the list's order.
+
+
+<h2>Common algorithms</h2>
+
+<h3>Assorted common algorithms</h3>
+
<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:
@@ -478,79 +552,34 @@
<li>Return true.
</ol>
-<!-- I don't remember why I wrote this. Keeping it around just in case it
-turns out to be useful.
-
-<p><var>node</var> is an <dfn>extraneous line break</dfn> if the following
-algorithm returns true:
-
-<p class=XXX>This is positively horrible. It attempts to move complicated CSS
-logic into DOM methods, and does so badly. Can we somehow make this less evil,
-preferably much less evil?
+<p>To move a [[node]] to a new location, <dfn>preserving ranges</dfn>, remove
+the [[node]] from its original [[parent]] (if any), then insert it in the new
+location. In doing so, however, ignore the regular [[rangemutationrules]], and
+instead follow these rules:
<ol>
- <li>If <var>node</var> is not a [[br]], return false.
-
- <li>Let <var>ancestor block</var> be <var>node</var>.
-
- <li>While <var>ancestor block</var> is an <span>inline node</span>, set
- <var>ancestor block</var> to its [[parent]].
-
- <li>Let <var>previous box</var> be <var>node</var>.
-
- <li>While <var>previous box</var> is equal to or an [[ancestor]] of
- <var>node</var>, set <var>previous box</var> to the [[node]] immediately
- before it in [[treeorder]], or null if there is no such [[node]].
-
- <li>Let <var>next box</var> be the [[node]] immediately after <var>node</var>
- in [[treeorder]], or null if there is such [[node]].
-
- <li>If <var>previous box</var> is null, or is not a [[descendant]] of
- <var>ancestor block</var>, or is not an <span>inline node</span>, or is a
- [[br]], return false.
- <!- -
- This means br either is the first thing in its block container that generates
- an inline box, or it's the first thing after another block container, or the
- first thing after a br. In any case, the line break will be visible.
-
- Otherwise, it will be invisible as long as it immediately precedes a block
- box boundary.
- - ->
-
- <li>If <var>ancestor block</var> is not null and <var>node</var> is the last
- [[descendant]] of <var>ancestor block</var>, return true.
- <!- - Precedes the end of ancestor block's box. - ->
-
- <li>If <var>next box</var> is not null and not an <span>inline node</span>,
- return true.
- <!- - Precedes the start of next box's box. - ->
-
- <li>Return false.
+ <li>Let <var>node</var> be the moved [[node]], <var>old parent</var> and
+ <var>old index</var> be the old [[parent]] (which may be null) and [[index]],
+ and <var>new parent</var> and <var>new index</var> be the new [[parent]] and
+ [[index]].
+
+ <li>If a [[boundarypoint]]'s [[bpnode]] is the same as or a [[descendant]] of
+ <var>node</var>, leave it unchanged, so it moves to the new location. <!--
+ This is actually implicit, but I state it anyway for completeness. -->
+
+ <li>If a [[boundarypoint]]'s [[bpnode]] is <var>new parent</var> and its
+ [[bpoffset]] is greater than <var>new index</var>, add one to its
+ [[bpoffset]].
+
+ <li>If a [[boundarypoint]]'s [[bpnode]] is <var>old parent</var> and its
+ [[bpoffset]] is <var>old index</var> or <var>old index</var> + 1, set its
+ [[bpnode]] to <var>new parent</var> and add <var>new index</var> −
+ <var>old index</var> to its [[bpoffset]].
+
+ <li>If a [[boundarypoint]]'s [[bpnode]] is <var>old parent</var> and its
+ [[bpoffset]] is greater than <var>old index</var> + 1, subtract one from its
+ [[bpoffset]].
</ol>
--->
-
-<p>Each [[htmldocument]] has a boolean <dfn>CSS styling flag</dfn> associated
-with it, which must initially be false. (<span>The <code
-title>styleWithCSS</code> command</span> can be used to modify or query it, by
-means of the <code>execCommand()</code> and <code>queryCommandState()</code>
-methods.)
-
-<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,
-if the author has overridden the method with a custom method, the standard
-method must be run rather than the custom one.
-
-<p>When a list or set of [[nodes]] is assigned to a variable without specifying
-the order, they must be initially in [[treeorder]], if they share a root.
-(If they don't share a root, the order will be specified.) When the user agent
-is instructed to run particular steps for each member of a list, it must do so
-sequentially in the list's order.
-
-
-<h2>Common algorithms</h2>
-
-<h3>Assorted common algorithms</h3>
<p>To <dfn>set the tag name</dfn> of an [[element]] <var>element</var> to
<var>new name</var>:
@@ -736,35 +765,6 @@
descendants</dfn>, <span>split the parent</span> of <var>node</var>'s
[[children]].
-<p>To move a [[node]] to a new location, <dfn>preserving ranges</dfn>, remove
-the [[node]] from its original [[parent]] (if any), then insert it in the new
-location. In doing so, however, ignore the regular [[rangemutationrules]], and
-instead follow these rules:
-
-<ol>
- <li>Let <var>node</var> be the moved [[node]], <var>old parent</var> and
- <var>old index</var> be the old [[parent]] (which may be null) and [[index]],
- and <var>new parent</var> and <var>new index</var> be the new [[parent]] and
- [[index]].
-
- <li>If a [[boundarypoint]]'s [[bpnode]] is the same as or a [[descendant]] of
- <var>node</var>, leave it unchanged, so it moves to the new location. <!--
- This is actually implicit, but I state it anyway for completeness. -->
-
- <li>If a [[boundarypoint]]'s [[bpnode]] is <var>new parent</var> and its
- [[bpoffset]] is greater than <var>new index</var>, add one to its
- [[bpoffset]].
-
- <li>If a [[boundarypoint]]'s [[bpnode]] is <var>old parent</var> and its
- [[bpoffset]] is <var>old index</var> or <var>old index</var> + 1, set its
- [[bpnode]] to <var>new parent</var> and add <var>new index</var> −
- <var>old index</var> to its [[bpoffset]].
-
- <li>If a [[boundarypoint]]'s [[bpnode]] is <var>old parent</var> and its
- [[bpoffset]] is greater than <var>old index</var> + 1, subtract one from its
- [[bpoffset]].
-</ol>
-
<h3>Wrapping a list of nodes</h3>
@@ -1366,6 +1366,9 @@
display property computes to something other than "inline", "inline-block", or
"inline-table"; or any [[node]] that is not <span>editable</span>.
+<p class=XXX>Probably want to redefine unwrappable node in terms of allowed
+children or something.
+
<p>A <dfn>modifiable element</dfn> is a [[b]], [[em]], [[i]], [[s]], [[span]],
[[strike]], [[strong]], [[sub]], [[sup]], or [[u]] element with no attributes
except possibly [[style]]; or a [[font]] element with no attributes except
@@ -2453,282 +2456,6 @@
<!-- I'd have expected the value to be the URL, but guess not. -->
-<h3><dfn>The <code title>delete</code> command</dfn></h3>
-<!-- Not really specifically "inline", but I didn't want to create a new
-section just for it. -->
-
-<p><span>Action</span>:
-
-<ol>
- <li>If the <span>active range</span> is not <code data-anolis-spec=domrange
- title=dom-Range-collapsed>collapsed</code>, <span>delete the contents</span>
- of the <span>active range</span> and abort these steps.
-
- <p class=XXX>Maybe we sometimes want to do the following even if it isn't
- collapsed? WebKit seems to do some normalization on the range before
- deciding whether it's collapsed, and that sounds like a good idea.
-
- <li>Let <var>node</var> and <var>offset</var> be the <span>active
- range</span>'s [[rangestart]] [[bpnode]] and [[bpoffset]].
-
- <!-- First go up as high as possible within the current block, then drill
- down to the lowest possible level, in the hopes that we'll wind up at the end
- of a text node, or maybe in a br or hr. -->
- <li>Repeat the following steps:
-
- <ol>
- <!--
- If there's an invisible node somewhere, Firefox 5.0a2 removes that node and
- then stops, so each backspace removes one invisible node. All others
- remove the invisible node and then continue on looking for something
- visible to remove. The spec follows the latter behavior, since it makes
- more sense to the user. Of course, the definition of "invisible node" is
- not necessarily anything like the spec's.
- -->
- <li>If <var>offset</var> is zero and <var>node</var>'s [[previoussibling]]
- is an <span>editable</span> <span>invisible node</span>, remove
- <var>node</var>'s [[previoussibling]] from its [[parent]].
-
- <li>Otherwise, if <var>node</var> has a [[child]] with [[index]]
- <var>offset</var> − 1 and that [[child]] is an <span>editable</span>
- <span>invisible node</span>, remove that [[child]] from <var>node</var>,
- then subtract one from <var>offset</var>.
-
- <li>Otherwise, if <var>offset</var> is zero and <var>node</var> is not a
- <span>prohibited paragraph child</span>, or if <var>node</var> is an
- <span>invisible node</span>, set <var>offset</var> to the [[index]] of
- <var>node</var>, then set <var>node</var> to its [[parent]].
-
- <li>Otherwise, if <var>node</var> has a [[child]] with [[index]]
- <var>offset</var> − 1 and that [[child]] is not a <span>prohibited
- paragraph child</span> or a [[br]] or an [[img]], set <var>node</var> to
- that [[child]], then set <var>offset</var> to the [[nodelength]] of
- <var>node</var>.
-
- <li>Otherwise, break from this loop.
- </ol>
-
- <!--
- At this point, node cannot be an invisible node. There are three cases:
-
- 1) offset is zero and node is a prohibited paragraph child. Then we'll
- usually merge with the previous block if one exists.
-
- 2) offset is not zero, node is not a prohibited paragraph child, and node
- does not have a child with index offset - 1. The only way this is possible
- is if node has a length greater than zero but no children, which implies it's
- a text or comment or PI. Comments and PIs are invisible nodes, so it must be
- a text node. We delete the previous character.
-
- 3) offset is not zero, and the child of node with index offset - 1 is a
- prohibited paragraph child or a br or an img. Then we'll usually merge the
- offsetth child of node with the last descendant of the offset - 1st.
- -->
-
- <li>If <var>node</var> is a [[text]] node and <var>offset</var> is not zero,
- call [[selcollapse|<var>node</var>, <var>offset</var>]] on the [[selection]].
- Then <span>delete the contents</span> of the [[range]] with [[rangestart]]
- (<var>node</var>, <var>offset</var> − 1) and [[rangeend]]
- (<var>node</var>, <var>offset</var>) and abort these steps.
-
- <!-- At the time of this writing, this should be impossible. -->
- <li>If <var>node</var> is not a <span>prohibited paragraph child</span>,
- abort these steps.
-
- <li>If <var>node</var> has a [[child]] with [[index]] <var>offset</var>
- − 1 and that [[child]] is a [[br]] or [[hr]] or [[img]], call
- [[selcollapse|<var>node</var>, <var>offset</var>]] on the [[selection]].
- Then <span>delete the contents</span> of the [[range]] with [[rangestart]]
- (<var>node</var>, <var>offset</var> − 1) and [[rangeend]]
- (<var>node</var>, <var>offset</var>) and abort these steps.
-
- <!--
- If we're at the beginning of a list, we want to outdent the first list item.
- This doesn't actually match anyone or anything. Word 2007 and OpenOffice.org
- 3.2.1 Ubuntu just remove the list marker, which is weird and doesn't map well
- to HTML. Browsers tend to just merge with the preceding block, which isn't
- expected.
- -->
- <li>If <var>node</var> is an [[li]] or [[dt]] or [[dd]] and is the first
- [[child]] of its [[parent]]:
-
- <ol>
- <li>Let <var>items</var> be a list of all [[li]]s that are
- [[ancestors]] of <var>node</var>.
-
- <li><span>Normalize sublists</span> of each <var>item</var> in
- <var>items</var>.
-
- <li><span>Split the parent</span> of the one-[[node]] list consisting of
- <var>node</var>.
-
- <li><span>Fix disallowed ancestors</span> of <var>node</var>.
-
- <li>Abort these steps.
- </ol>
-
- <!-- By this point, we're almost certainly going to merge something, and the
- only question is what. -->
- <li>Let <var>start node</var> equal <var>node</var> and let <var>start
- offset</var> equal <var>offset</var>.
-
- <li>While <var>start offset</var> is zero, set <var>start offset</var> to the
- [[index]] of <var>start node</var> and then set <var>start node</var> to its
- [[parent]].
-
- <p class=XXX>The node at index start offset − 1 might be an invisible
- node.
-
- <!--
- At the beginning of an indented block, outdent it, similar to a list item.
- Browsers don't do this, word processors do.
- -->
- <li>If <var>offset</var> is zero, and <var>node</var> has an
- [[ancestorcontainer]] that is both a <span>potential indentation
- element</span> and a [[descendant]] of <var>start node</var>:
-
- <p class=XXX>This copy-pastes from the outdent command action. I'm also not
- totally sure it's correct.
-
- <ol>
- <li><span>Block-extend</span> the [[range]] whose [[rangestart]] and
- [[rangeend]] are both (<var>node</var>, 0), and let <var>new range</var> be
- the result.
-
- <li>Let <var>node list</var> be a list of [[nodes]], initially empty.
-
- <li>For each [[node]] <var>current node</var> [[contained]] in <var>new
- range</var>, append <var>current node</var> to <var>node list</var> if the
- last member of <var>node list</var> (if any) is not an [[ancestor]] of
- <var>current node</var>, and <var>current node</var> is
- <span>editable</span> but has no <span>editable</span> [[descendants]].
-
- <li><span>Outdent</span> each [[node]] in <var>node list</var>.
-
- <li>Abort these steps.
- </ol>
-
- <!--
- This is to avoid stripping a line break from
-
- foo<br><br><table><tr><td>[]bar</table>
-
- and similarly for <hr>. We should just do nothing here.
- -->
- <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
- offset</var> is a [[table]], abort these steps.
-
- <!--
- If you try backspacing into a table, select it. This doesn't match any
- browser; it matches the recommendation of the "behavior when typing in
- contentEditable elements" document. The idea is that then you can delete it
- with a second backspace.
- -->
- <li>If <var>start node</var> has a [[child]] with [[index]] <var>start
- offset</var> − 1, and that [[child]] is a [[table]]:
-
- <ol>
- <li>Call [[selcollapse|<var>start node</var>, <var>start offset</var>
- − 1]] on the [[contextobject]]'s [[selection]].
-
- <li>Call [[extend|<var>start node</var>, <var>start offset</var>]] on the
- [[contextobject]]'s [[selection]].
-
- <li>Abort these steps.
- </ol>
-
- <!--
- Special case:
-
- <p>foo</p><br><p>[]bar</p>
- -> <p>foo</p><p>[]bar</p>
-
- and likewise for <hr>. But with <img> we merge like in other cases:
-
- <p>foo</p><img><p>[]bar</p>
- -> <p>foo</p><img>[]bar.
-
- Browsers don't do this consistently. Firefox 5.0a2 doesn't seem to do it at
- all.
- -->
- <li>If <var>offset</var> is zero; and either the [[child]] of <var>start
- node</var> with [[index]] <var>start offset</var> minus one is an [[hr]], or
- the [[child]] is a [[br]] whose [[previoussibling]] is either a [[br]] or not
- an <span>inline node</span>:
-
- <ol>
- <li>Call [[selcollapse|<var>node</var>, <var>offset</var>]] on the
- [[selection]].
-
- <li><span>Delete the contents</span> of the [[range]] with [[rangestart]]
- (<var>start node</var>, <var>start offset</var> − 1) and [[rangeend]]
- (<var>start node</var>, <var>start offset</var>).
-
- <li>Abort these steps.
- </ol>
-
- <!--
- If you try backspacing out of a list item, merge it with the previous item,
- but add a line break. Then you have to backspace again if you really want
- them to be on the same line. This matches Word 2007 and OpenOffice.org 3.2.1
- Ubuntu, and also matches "behavior when typing in contentEditable elements",
- but does not match any browser.
-
- Note that this behavior is quite different from what happens if you actually
- select the linebreak in between the two lines. In that case, the blocks are
- merged as normal.
-
- Also note that hitting backspace twice will merge with the previous item.
- This matches OO.org, but Word will outdent the item on subsequent backspaces.
- Word's behavior doesn't fit well with the way lists work in HTML, and we
- probably don't want it.
- -->
- <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
- offset</var> is an [[li]] or [[dt]] or [[dd]], and that [[child]]'s
- [[firstchild]] is an <span>inline node</span>, and <var>start offset</var> is
- not zero:
-
- <ol>
- <li>Let <var>previous item</var> be the [[child]] of <var>start node</var>
- with [[index]] <var>start offset</var> minus one.
-
- <!-- If the last child is already a br, we only need to append one extra
- br. Otherwise we need to append two, since the first will do nothing. -->
- <li>If <var>previous item</var>'s [[lastchild]] is an <span>inline
- node</span> other than a [[br]], call [[createelement|"br"]] on the
- [[contextobject]] and append the result as the last [[child]] of
- <var>previous item</var>.
-
- <li>If <var>previous item</var>'s [[lastchild]] is an <span>inline
- node</span>, call [[createelement|"br"]] on the [[contextobject]] and
- append the result as the last [[child]] of <var>previous item</var>.
- </ol>
-
- <!--
- When merging adjacent list items, make sure we only merge the items
- themselves, not any block children. We want <li><p>foo<li><p>bar to become
- <li><p>foo<p>bar, not <li><p>foo<br>bar or <li><p>foobar.
- -->
- <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
- offset</var> is an [[li]] or [[dt]] or [[dd]], and its [[previoussibling]] is
- also an [[li]] or [[dt]] or [[dd]], set <var>start node</var> to its
- [[child]] with [[index]] <var>start offset</var> − 1, then set
- <var>start offset</var> to <var>start node</var>'s [[nodelength]], then set
- <var>node</var> to <var>start node</var>'s [[nextsibling]], then set
- <var>offset</var> to 0.
-
- <!-- General block-merging case. -->
- <li>Otherwise, while <var>start node</var> has a [[child]] with [[index]]
- <var>start offset</var> minus one, set <var>start node</var> to that
- [[child]], then set <var>start offset</var> to the [[nodelength]] of
- <var>start node</var>.
-
- <li><span>Delete the contents</span> of the [[range]] with [[rangestart]]
- (<var>start node</var>, <var>start offset</var>) and [[rangeend]]
- (<var>node</var>, <var>offset</var>).
-</ol>
-
-
<h3><dfn>The <code title>fontName</code> command</dfn></h3>
<p><span>Action</span>: <span>Decompose</span> the <span>active range</span>,
@@ -3740,6 +3467,9 @@
<h3>Block-formatting a node list</h3>
+<p class=XXX>Why is this a separate section? There's only one caller.
+Probably want to merge it back.
+
<p>To <dfn>block-format</dfn> a list of [[nodes]] <var>input nodes</var> to a
string <var>value</var>:
@@ -4728,6 +4458,280 @@
</ol>
+<h3><dfn>The <code title>delete</code> command</dfn></h3>
+
+<p><span>Action</span>:
+
+<ol>
+ <li>If the <span>active range</span> is not <code data-anolis-spec=domrange
+ title=dom-Range-collapsed>collapsed</code>, <span>delete the contents</span>
+ of the <span>active range</span> and abort these steps.
+
+ <p class=XXX>Maybe we sometimes want to do the following even if it isn't
+ collapsed? WebKit seems to do some normalization on the range before
+ deciding whether it's collapsed, and that sounds like a good idea.
+
+ <li>Let <var>node</var> and <var>offset</var> be the <span>active
+ range</span>'s [[rangestart]] [[bpnode]] and [[bpoffset]].
+
+ <!-- First go up as high as possible within the current block, then drill
+ down to the lowest possible level, in the hopes that we'll wind up at the end
+ of a text node, or maybe in a br or hr. -->
+ <li>Repeat the following steps:
+
+ <ol>
+ <!--
+ If there's an invisible node somewhere, Firefox 5.0a2 removes that node and
+ then stops, so each backspace removes one invisible node. All others
+ remove the invisible node and then continue on looking for something
+ visible to remove. The spec follows the latter behavior, since it makes
+ more sense to the user. Of course, the definition of "invisible node" is
+ not necessarily anything like the spec's.
+ -->
+ <li>If <var>offset</var> is zero and <var>node</var>'s [[previoussibling]]
+ is an <span>editable</span> <span>invisible node</span>, remove
+ <var>node</var>'s [[previoussibling]] from its [[parent]].
+
+ <li>Otherwise, if <var>node</var> has a [[child]] with [[index]]
+ <var>offset</var> − 1 and that [[child]] is an <span>editable</span>
+ <span>invisible node</span>, remove that [[child]] from <var>node</var>,
+ then subtract one from <var>offset</var>.
+
+ <li>Otherwise, if <var>offset</var> is zero and <var>node</var> is not a
+ <span>prohibited paragraph child</span>, or if <var>node</var> is an
+ <span>invisible node</span>, set <var>offset</var> to the [[index]] of
+ <var>node</var>, then set <var>node</var> to its [[parent]].
+
+ <li>Otherwise, if <var>node</var> has a [[child]] with [[index]]
+ <var>offset</var> − 1 and that [[child]] is not a <span>prohibited
+ paragraph child</span> or a [[br]] or an [[img]], set <var>node</var> to
+ that [[child]], then set <var>offset</var> to the [[nodelength]] of
+ <var>node</var>.
+
+ <li>Otherwise, break from this loop.
+ </ol>
+
+ <!--
+ At this point, node cannot be an invisible node. There are three cases:
+
+ 1) offset is zero and node is a prohibited paragraph child. Then we'll
+ usually merge with the previous block if one exists.
+
+ 2) offset is not zero, node is not a prohibited paragraph child, and node
+ does not have a child with index offset - 1. The only way this is possible
+ is if node has a length greater than zero but no children, which implies it's
+ a text or comment or PI. Comments and PIs are invisible nodes, so it must be
+ a text node. We delete the previous character.
+
+ 3) offset is not zero, and the child of node with index offset - 1 is a
+ prohibited paragraph child or a br or an img. Then we'll usually merge the
+ offsetth child of node with the last descendant of the offset - 1st.
+ -->
+
+ <li>If <var>node</var> is a [[text]] node and <var>offset</var> is not zero,
+ call [[selcollapse|<var>node</var>, <var>offset</var>]] on the [[selection]].
+ Then <span>delete the contents</span> of the [[range]] with [[rangestart]]
+ (<var>node</var>, <var>offset</var> − 1) and [[rangeend]]
+ (<var>node</var>, <var>offset</var>) and abort these steps.
+
+ <!-- At the time of this writing, this should be impossible. -->
+ <li>If <var>node</var> is not a <span>prohibited paragraph child</span>,
+ abort these steps.
+
+ <li>If <var>node</var> has a [[child]] with [[index]] <var>offset</var>
+ − 1 and that [[child]] is a [[br]] or [[hr]] or [[img]], call
+ [[selcollapse|<var>node</var>, <var>offset</var>]] on the [[selection]].
+ Then <span>delete the contents</span> of the [[range]] with [[rangestart]]
+ (<var>node</var>, <var>offset</var> − 1) and [[rangeend]]
+ (<var>node</var>, <var>offset</var>) and abort these steps.
+
+ <!--
+ If we're at the beginning of a list, we want to outdent the first list item.
+ This doesn't actually match anyone or anything. Word 2007 and OpenOffice.org
+ 3.2.1 Ubuntu just remove the list marker, which is weird and doesn't map well
+ to HTML. Browsers tend to just merge with the preceding block, which isn't
+ expected.
+ -->
+ <li>If <var>node</var> is an [[li]] or [[dt]] or [[dd]] and is the first
+ [[child]] of its [[parent]]:
+
+ <ol>
+ <li>Let <var>items</var> be a list of all [[li]]s that are
+ [[ancestors]] of <var>node</var>.
+
+ <li><span>Normalize sublists</span> of each <var>item</var> in
+ <var>items</var>.
+
+ <li><span>Split the parent</span> of the one-[[node]] list consisting of
+ <var>node</var>.
+
+ <li><span>Fix disallowed ancestors</span> of <var>node</var>.
+
+ <li>Abort these steps.
+ </ol>
+
+ <!-- By this point, we're almost certainly going to merge something, and the
+ only question is what. -->
+ <li>Let <var>start node</var> equal <var>node</var> and let <var>start
+ offset</var> equal <var>offset</var>.
+
+ <li>While <var>start offset</var> is zero, set <var>start offset</var> to the
+ [[index]] of <var>start node</var> and then set <var>start node</var> to its
+ [[parent]].
+
+ <p class=XXX>The node at index start offset − 1 might be an invisible
+ node.
+
+ <!--
+ At the beginning of an indented block, outdent it, similar to a list item.
+ Browsers don't do this, word processors do.
+ -->
+ <li>If <var>offset</var> is zero, and <var>node</var> has an
+ [[ancestorcontainer]] that is both a <span>potential indentation
+ element</span> and a [[descendant]] of <var>start node</var>:
+
+ <p class=XXX>This copy-pastes from the outdent command action. I'm also not
+ totally sure it's correct.
+
+ <ol>
+ <li><span>Block-extend</span> the [[range]] whose [[rangestart]] and
+ [[rangeend]] are both (<var>node</var>, 0), and let <var>new range</var> be
+ the result.
+
+ <li>Let <var>node list</var> be a list of [[nodes]], initially empty.
+
+ <li>For each [[node]] <var>current node</var> [[contained]] in <var>new
+ range</var>, append <var>current node</var> to <var>node list</var> if the
+ last member of <var>node list</var> (if any) is not an [[ancestor]] of
+ <var>current node</var>, and <var>current node</var> is
+ <span>editable</span> but has no <span>editable</span> [[descendants]].
+
+ <li><span>Outdent</span> each [[node]] in <var>node list</var>.
+
+ <li>Abort these steps.
+ </ol>
+
+ <!--
+ This is to avoid stripping a line break from
+
+ foo<br><br><table><tr><td>[]bar</table>
+
+ and similarly for <hr>. We should just do nothing here.
+ -->
+ <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
+ offset</var> is a [[table]], abort these steps.
+
+ <!--
+ If you try backspacing into a table, select it. This doesn't match any
+ browser; it matches the recommendation of the "behavior when typing in
+ contentEditable elements" document. The idea is that then you can delete it
+ with a second backspace.
+ -->
+ <li>If <var>start node</var> has a [[child]] with [[index]] <var>start
+ offset</var> − 1, and that [[child]] is a [[table]]:
+
+ <ol>
+ <li>Call [[selcollapse|<var>start node</var>, <var>start offset</var>
+ − 1]] on the [[contextobject]]'s [[selection]].
+
+ <li>Call [[extend|<var>start node</var>, <var>start offset</var>]] on the
+ [[contextobject]]'s [[selection]].
+
+ <li>Abort these steps.
+ </ol>
+
+ <!--
+ Special case:
+
+ <p>foo</p><br><p>[]bar</p>
+ -> <p>foo</p><p>[]bar</p>
+
+ and likewise for <hr>. But with <img> we merge like in other cases:
+
+ <p>foo</p><img><p>[]bar</p>
+ -> <p>foo</p><img>[]bar.
+
+ Browsers don't do this consistently. Firefox 5.0a2 doesn't seem to do it at
+ all.
+ -->
+ <li>If <var>offset</var> is zero; and either the [[child]] of <var>start
+ node</var> with [[index]] <var>start offset</var> minus one is an [[hr]], or
+ the [[child]] is a [[br]] whose [[previoussibling]] is either a [[br]] or not
+ an <span>inline node</span>:
+
+ <ol>
+ <li>Call [[selcollapse|<var>node</var>, <var>offset</var>]] on the
+ [[selection]].
+
+ <li><span>Delete the contents</span> of the [[range]] with [[rangestart]]
+ (<var>start node</var>, <var>start offset</var> − 1) and [[rangeend]]
+ (<var>start node</var>, <var>start offset</var>).
+
+ <li>Abort these steps.
+ </ol>
+
+ <!--
+ If you try backspacing out of a list item, merge it with the previous item,
+ but add a line break. Then you have to backspace again if you really want
+ them to be on the same line. This matches Word 2007 and OpenOffice.org 3.2.1
+ Ubuntu, and also matches "behavior when typing in contentEditable elements",
+ but does not match any browser.
+
+ Note that this behavior is quite different from what happens if you actually
+ select the linebreak in between the two lines. In that case, the blocks are
+ merged as normal.
+
+ Also note that hitting backspace twice will merge with the previous item.
+ This matches OO.org, but Word will outdent the item on subsequent backspaces.
+ Word's behavior doesn't fit well with the way lists work in HTML, and we
+ probably don't want it.
+ -->
+ <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
+ offset</var> is an [[li]] or [[dt]] or [[dd]], and that [[child]]'s
+ [[firstchild]] is an <span>inline node</span>, and <var>start offset</var> is
+ not zero:
+
+ <ol>
+ <li>Let <var>previous item</var> be the [[child]] of <var>start node</var>
+ with [[index]] <var>start offset</var> minus one.
+
+ <!-- If the last child is already a br, we only need to append one extra
+ br. Otherwise we need to append two, since the first will do nothing. -->
+ <li>If <var>previous item</var>'s [[lastchild]] is an <span>inline
+ node</span> other than a [[br]], call [[createelement|"br"]] on the
+ [[contextobject]] and append the result as the last [[child]] of
+ <var>previous item</var>.
+
+ <li>If <var>previous item</var>'s [[lastchild]] is an <span>inline
+ node</span>, call [[createelement|"br"]] on the [[contextobject]] and
+ append the result as the last [[child]] of <var>previous item</var>.
+ </ol>
+
+ <!--
+ When merging adjacent list items, make sure we only merge the items
+ themselves, not any block children. We want <li><p>foo<li><p>bar to become
+ <li><p>foo<p>bar, not <li><p>foo<br>bar or <li><p>foobar.
+ -->
+ <li>If the [[child]] of <var>start node</var> with [[index]] <var>start
+ offset</var> is an [[li]] or [[dt]] or [[dd]], and its [[previoussibling]] is
+ also an [[li]] or [[dt]] or [[dd]], set <var>start node</var> to its
+ [[child]] with [[index]] <var>start offset</var> − 1, then set
+ <var>start offset</var> to <var>start node</var>'s [[nodelength]], then set
+ <var>node</var> to <var>start node</var>'s [[nextsibling]], then set
+ <var>offset</var> to 0.
+
+ <!-- General block-merging case. -->
+ <li>Otherwise, while <var>start node</var> has a [[child]] with [[index]]
+ <var>start offset</var> minus one, set <var>start node</var> to that
+ [[child]], then set <var>start offset</var> to the [[nodelength]] of
+ <var>start node</var>.
+
+ <li><span>Delete the contents</span> of the [[range]] with [[rangestart]]
+ (<var>start node</var>, <var>start offset</var>) and [[rangeend]]
+ (<var>node</var>, <var>offset</var>).
+</ol>
+
+
<h3><dfn>The <code title>formatBlock</code> command</dfn></h3>
<!--
Tested browser versions: IE9, Firefox 4.0, Chrome 13 dev, Opera 11.10.