--- a/conformancetest/data.js Thu Oct 27 15:16:47 2011 -0600
+++ b/conformancetest/data.js Mon Nov 07 16:01:08 2011 -0700
@@ -3681,11 +3681,11 @@
{"stylewithcss":[false,false,"",false,true,""],"delete":[false,false,"",false,false,""]}],
["foo[<br>]bar",
[["stylewithcss","false"],["delete",""]],
- "foo[]bar",
+ "foo{}bar",
{"stylewithcss":[false,true,"",false,false,""],"delete":[false,false,"",false,false,""]}],
["foo[<br>]bar",
[["stylewithcss","true"],["delete",""]],
- "foo[]bar",
+ "foo{}bar",
{"stylewithcss":[false,false,"",false,true,""],"delete":[false,false,"",false,false,""]}],
["<p>foo[</p><p>]bar</p>",
[["stylewithcss","false"],["delete",""]],
@@ -9913,11 +9913,11 @@
{"stylewithcss":[false,false,"",false,true,""],"forwarddelete":[false,false,"",false,false,""]}],
["foo[<br>]bar",
[["stylewithcss","false"],["forwarddelete",""]],
- "foo[]bar",
+ "foo{}bar",
{"stylewithcss":[false,true,"",false,false,""],"forwarddelete":[false,false,"",false,false,""]}],
["foo[<br>]bar",
[["stylewithcss","true"],["forwarddelete",""]],
- "foo[]bar",
+ "foo{}bar",
{"stylewithcss":[false,false,"",false,true,""],"forwarddelete":[false,false,"",false,false,""]}],
["<p>foo[</p><p>]bar</p>",
[["stylewithcss","false"],["forwarddelete",""]],
--- a/editing.html Thu Oct 27 15:16:47 2011 -0600
+++ b/editing.html Mon Nov 07 16:01:08 2011 -0700
@@ -67,7 +67,7 @@
<body class=draft>
<div class=head id=head>
<h1>HTML Editing APIs</h1>
-<h2 class="no-num no-toc" id=work-in-progress-—-last-update-27-october-2011>Work in Progress — Last Update 27 October 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-—-last-update-7-november-2011>Work in Progress — Last Update 7 November 2011</h2>
<dl>
<dt>Editor
<dd>Aryeh Gregor <<a href=mailto:ayg@aryeh.name>ayg@aryeh.name</a>>
@@ -336,7 +336,7 @@
bad, <code title=""><b>{}</b></code> is correct.
<li>Curly braces mark a selection inside an element, like
- <code title=""><b>{foobarbaz</b>} </code> for a selection whose start node is the element
+ <code title=""><b>{foobarbaz</b>}</code> for a selection whose start node is the element
<code title=""><b></code>, whose start offset is 0, whose end node is the root of the
editable region, and whose end offset is 1. Do not use curly braces in the
middle of a text node: <code title="">foo{bar}baz</code> is bad, <code title="">foo[bar]baz</code> is correct.
@@ -5160,6 +5160,104 @@
<!--@}-->
<h3 id=deleting-the-selection>Deleting the selection</h3>
+<p class=note>Sometimes one location corresponds to multiple distinct boundary
+points. For instance, in the DOM <code title=""><p>Hello</p></code>, a boundary point might
+lie at the beginning of the text node or the beginning of the element node, but
+these don't logically differ much and will appear the same to the user, so we
+often want to treat them the same. The algorithms here (currently used only
+for the delete algorithm) allow navigating through such equivalent boundary
+points, for when we want to make the selection as inclusive or exclusive as
+possible. For deletion, we want to delete as few nodes as possible, so we move
+the start node forward and the end node backward. In other cases we might do
+the reverse, expanding the selection.
+
+<p>Given a <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-bp title=concept-range-bp>boundary point</a> (<var title="">node</var>, <var title="">offset</var>), the
+<dfn id=next-equivalent-point>next equivalent point</dfn> is either a <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-bp title=concept-range-bp>boundary point</a> or null, as
+returned by the following algorithm:
+
+<ol>
+ <li>If <var title="">node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length title=concept-node-length>length</a> is zero, return null.
+
+ <li class=note>We don't want to move into or out of zero-length nodes,
+ because that would move us straight through them. For instance, if
+ <code title="">{}<span></span></code> were equivalent to <code title=""><span>{}</span></code>, it
+ would also be equivalent to <code title=""><span></span>{}</code>. This produces very
+ unexpected results for nodes like <code title=""><br></code>.
+
+ <li>If <var title="">offset</var> is <var title="">node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length title=concept-node-length>length</a>, and
+ <var title="">node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is not null, return (<var title="">node</var>'s
+ <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>, 1 + <var title="">node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-index title=concept-tree-index>index</a>).
+
+ <li class=note>For instance, <code title=""><span>foo[]</span></code> is equivalent to
+ <code title=""><span>foo{}</span></code>, which is equivalent to <code title=""><span>foo</span>{}</code>.
+
+ <li>If <var title="">node</var> has a <a class=external data-anolis-spec=dom 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=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-index title=concept-tree-index>index</a> <var title="">offset</var>,
+ and that <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length title=concept-node-length>length</a> is not zero, return (that <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>, 0).
+
+ <li class=note>For instance, <code title="">{}<span>foo</span></code> is equivalent to
+ <code title=""><span>{}foo</span></code>, which is equivalent to
+ <code title=""><span>[]foo</span></code>. As noted before, though, we don't descend into
+ empty nodes.
+
+ <li>Return null.
+</ol>
+
+<p>Given a <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-bp title=concept-range-bp>boundary point</a> (<var title="">node</var>, <var title="">offset</var>), the
+<dfn id=previous-equivalent-point>previous equivalent point</dfn> is either a <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-bp title=concept-range-bp>boundary point</a> or null, as
+returned by the following algorithm:
+
+<ol>
+ <li>If <var title="">node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length title=concept-node-length>length</a> is zero, return null.
+
+ <li>If <var title="">offset</var> is 0, and <var title="">node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is not null,
+ return (<var title="">node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>, <var title="">node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-index title=concept-tree-index>index</a>).
+
+ <li>If <var title="">node</var> has a <a class=external data-anolis-spec=dom 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=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-index title=concept-tree-index>index</a> <var title="">offset</var>
+ − 1, and that <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length title=concept-node-length>length</a> is not zero, return (that
+ <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>, that <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length title=concept-node-length>length</a>).
+
+ <li>Return null.
+</ol>
+
+<!-- Turned out not to be necessary here, but I've left it in case it turns out
+to be handy elsewhere. -->
+<!--
+<p>Two <span data-anolis-spec=dom title=concept-range-bp>boundary points</span> <var title>A</var> and <var title>B</var> are <dfn>equivalent
+boundary points</dfn> if the following algorithm returns true:
+
+<p class=note>This is indeed an <a
+href=http://en.wikipedia.org/wiki/Equivalence_relation>equivalence
+relation</a>. Reflexivity, symmetry, and transitivity are all fairly obvious
+from the definition. It also isn't hard to check that using <span>next
+equivalent point</span> instead of <span>previous equivalent point</span> would
+yield the same definition, because the two algorithms are inverses if neither
+output is null.
+
+<ol>
+ <li>While <var title>A</var>'s <span>previous equivalent point</span> is not null,
+ set <var title>A</var> to its <span>previous equivalent point</span>.
+
+ <li>While <var title>B</var>'s <span>previous equivalent point</span> is not null,
+ set <var title>B</var> to its <span>previous equivalent point</span>.
+
+ <li>Return true if <var title>A</var> is the same as <var title>B</var>, otherwise false.
+</ol>
+-->
+
+<p>The <dfn id=block-node-of>block node of</dfn> a <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> <var title="">node</var> is either a
+<a href=#block-node>block node</a> or null, as returned by the following algorithm:
+
+<ol>
+ <li>While <var title="">node</var> is an <a href=#inline-node>inline node</a>, set <var title="">node</var>
+ to its <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+ <li>Return <var title="">node</var>.
+</ol>
+
+<p>Two <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a> are <dfn id=in-the-same-block>in the same block</dfn> if the <a href=#block-node-of>block node
+of</a> the first is non-null and the same as the <a href=#block-node-of>block node of</a>
+the second.
+
<p class=comments>TODO: Consider what should happen for block merging in corner
cases like display: inline-table.
@@ -5175,7 +5273,7 @@
selection will be collapsed. By way of contrast, <a href=#effectively-contained>effectively
contained</a> tries to expand the range to include as much as possible, so
<code title=""><p>[foo]</p></code> contains the <code title=""><p></code>. What we do here is contract
-the range to include as little as possible, so <code title="">{<p>foo</p>} </code> contains
+the range to include as little as possible, so <code title="">{<p>foo</p>}</code> contains
only <code title="">foo</code> and doesn't delete the paragraph.
<p>After that, if the selection originally started and ended in different
@@ -5219,104 +5317,21 @@
and <var title="">end offset</var> be the <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-start title=concept-range-start>start</a>
and <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-end title=concept-range-end>end</a> <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a> and <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-bp-offset title=concept-range-bp-offset>offsets</a>.
- <li class=note>
- <p>First we adjust the selection so that we don't delete more than we should.
-
- <li>
- <div class=comments>
- <p>We don't want to keep going when we hit an element with no children,
- because then we'd do something like
-
-<pre>foo{<br />bar]
--> foo<br>{</br>bar]
--> foo<br />{bar]
--> foo<br />[bar]</pre>
-
- <p>and we deselected the <code title=""><br></code>.
- </div>
-
- <p>While <var title="">start node</var> has at least one <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>:
-
- <ol>
- <li>
- <div class=comments>
- <p>For instance:
-
-<pre><b>foo[</b><i>bar]</i>
--> <b>foo{</b><i>bar]</i>
--> <b>foo</b>{<i>bar]</i></pre>
-
- <p>Then the next step will make it <code title=""><b>foo</b><i>[bar]</i></code>.
-
- <p>We don't want to do this for block nodes, because that would lead to
- something like
-
- <pre><p>foo[</p><p>]bar<p></pre>
-
- <p>ultimately collapsing, which is wrong. Once we do the deletion, it
- needs to wind up <code title=""><p>foo[]bar</p></code>, whereas an actually collapsed
- selection should do nothing.
- </div>
-
- <p>If <var title="">start offset</var> is <var title="">start node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length title=concept-node-length>length</a>, and
- <var title="">start node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is <a href=#in-the-same-editing-host>in the same editing
- host</a>, and <var title="">start node</var> is an <a href=#inline-node>inline node</a>, set
- <var title="">start offset</var> to one plus the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-index title=concept-tree-index>index</a> of <var title="">start node</var>,
- then set <var title="">start node</var> to its <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> and continue this loop
- from the beginning.
-
- <li>
- <p class=comments>This happens if the first step brought us all the way up
- to the root. The step immediately after this loop will bring us back down
- again.
-
- <p>If <var title="">start offset</var> is <var title="">start node</var>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length title=concept-node-length>length</a>,
- break from this loop.
-
- <li>Let <var title="">reference node</var> be the <a class=external data-anolis-spec=dom 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=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-index title=concept-tree-index>index</a> equal to <var title="">start offset</var>.
-
- <li>
- <div class=comments>
- <p>Don't descend into an element with no children, since then it won't get
- deleted even if it's selected. Don't descend into a block node, because
- then we might wind up not merging blocks when we should, e.g.
-
-<pre>foo{<p>}bar</p>
--> foo<p>{}bar</p></pre>
-
- <p>and nothing gets changed.
- </div>
-
- <p>If <var title="">reference node</var> is a <a href=#block-node>block node</a> or an
- <code class=external data-anolis-spec=dom><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> with no <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, or is neither an <code class=external data-anolis-spec=dom><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> nor a
- <code class=external data-anolis-spec=dom><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node, break from this loop.
-
- <li>Set <var title="">start node</var> to <var title="">reference node</var> and <var title="">start
- offset</var> to 0.
- </ol>
-
- <li>While <var title="">end node</var> has at least one <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a>:
-
- <ol>
- <li>If <var title="">end offset</var> is 0, and <var title="">end node</var>'s
- <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is <a href=#in-the-same-editing-host>in the same editing host</a>, and <var title="">end
- node</var> is an <a href=#inline-node>inline node</a>, set <var title="">end offset</var> to the
- <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-index title=concept-tree-index>index</a> of <var title="">end node</var>, then set <var title="">end node</var> to its
- <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> and continue this loop from the beginning.
-
- <li>If <var title="">end offset</var> is 0, break from this loop.
-
- <li>Let <var title="">reference node</var> be the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">end node</var>
- with <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-index title=concept-tree-index>index</a> equal to <var title="">end offset</var> minus one.
-
- <li>If <var title="">reference node</var> is a <a href=#block-node>block node</a> or an
- <code class=external data-anolis-spec=dom><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> with no <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>, or is neither an <code class=external data-anolis-spec=dom><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> nor a
- <code class=external data-anolis-spec=dom><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node, break from this loop.
-
- <li>Set <var title="">end node</var> to <var title="">reference node</var> and <var title="">end
- offset</var> to the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length title=concept-node-length>length</a> of <var title="">reference node</var>.
- </ol>
+ <li>While the <a href=#next-equivalent-point>next equivalent point</a> for (<var title="">start node</var>,
+ <var title="">start offset</var>) is not null, and that <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-bp title=concept-range-bp>boundary point</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>
+ is <a href=#in-the-same-editing-host>in the same editing host</a> and <a href=#in-the-same-block>in the same block</a>
+ as <var title="">start node</var>, set (<var title="">start node</var>, <var title="">start
+ offset</var>) to its <a href=#next-equivalent-point>next equivalent point</a>.
+
+ <li class=note>We don't want to leave the current block because otherwise,
+ for instance, a selection of <code title=""><p>foo[</p><p>]bar</p></code> would be treated
+ as empty and nothing would be deleted, when we want it to merge the blocks.
+
+ <li>While the <a href=#previous-equivalent-point>previous equivalent point</a> for (<var title="">end
+ node</var>, <var title="">end offset</var>) is not null, and that <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-bp title=concept-range-bp>boundary point</a>'s
+ <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> is <a href=#in-the-same-editing-host>in the same editing host</a> and <a href=#in-the-same-block>in the same
+ block</a> as <var title="">end node</var>, set (<var title="">end node</var>, <var title="">end
+ offset</var>) to its <a href=#previous-equivalent-point>previous equivalent point</a>.
<li>If (<var title="">end node</var>, <var title="">end offset</var>) is not <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-bp-after title=concept-range-bp-after>after</a>
(<var title="">start node</var>, <var title="">start offset</var>):
--- a/implementation.js Thu Oct 27 15:16:47 2011 -0600
+++ b/implementation.js Mon Nov 07 16:01:08 2011 -0700
@@ -4372,6 +4372,71 @@
///// Deleting the selection /////
//@{
+function getNextEquivalentPoint(node, offset) {
+ // "If node's length is zero, return null."
+ if (getNodeLength(node) == 0) {
+ return null;
+ }
+
+ // "If offset is node's length, and node's parent is not null, return
+ // (node's parent, 1 + node's index)."
+ if (offset == getNodeLength(node) && node.parentNode) {
+ return [node.parentNode, 1 + getNodeIndex(node)];
+ }
+
+ // "If node has a child with index offset, and that child's length is not
+ // zero, return (that child, 0)."
+ if (0 <= node.childNodes.length
+ && offset < node.childNodes.length
+ && getNodeLength(node.childNodes[offset]) != 0) {
+ return [node.childNodes[offset], 0];
+ }
+
+ // "Return null."
+ return null;
+}
+
+function getPreviousEquivalentPoint(node, offset) {
+ // "If node's length is zero, return null."
+ if (getNodeLength(node) == 0) {
+ return null;
+ }
+
+ // "If offset is 0, and node's parent is not null, return (node's parent,
+ // node's index)."
+ if (offset == 0 && node.parentNode) {
+ return [node.parentNode, getNodeIndex(node)];
+ }
+
+ // "If node has a child with index offset − 1, and that child's length is
+ // not zero, return (that child, that child's length)."
+ if (0 <= node.childNodes.length
+ && offset - 1 < node.childNodes.length
+ && getNodeLength(node.childNodes[offset - 1]) != 0) {
+ return [node.childNodes[offset - 1], getNodeLength(node.childNodes[offset - 1])];
+ }
+
+ // "Return null."
+ return null;
+}
+
+function getBlockNodeOf(node) {
+ // "While node is an inline node, set node to its parent."
+ while (isInlineNode(node)) {
+ node = node.parentNode;
+ }
+
+ // "Return node."
+ return node;
+}
+
+// "Two nodes are in the same block if the block node of the first is non-null
+// and the same as the block node of the second."
+function inSameBlock(node1, node2) {
+ return getBlockNodeOf(node1)
+ && getBlockNodeOf(node1) === getBlockNodeOf(node2);
+}
+
// The flags argument is a dictionary that can have blockMerging,
// stripWrappers, and/or direction as keys.
function deleteSelection(flags) {
@@ -4401,81 +4466,28 @@
var endNode = getActiveRange().endContainer;
var endOffset = getActiveRange().endOffset;
- // "While start node has at least one child:"
- while (startNode.hasChildNodes()) {
- // "If start offset is start node's length, and start node's parent is
- // in the same editing host, and start node is an inline node, set
- // start offset to one plus the index of start node, then set start
- // node to its parent and continue this loop from the beginning."
- if (startOffset == getNodeLength(startNode)
- && inSameEditingHost(startNode, startNode.parentNode)
- && isInlineNode(startNode)) {
- startOffset = 1 + getNodeIndex(startNode);
- startNode = startNode.parentNode;
- continue;
- }
-
- // "If start offset is start node's length, break from this loop."
- if (startOffset == getNodeLength(startNode)) {
- break;
- }
-
- // "Let reference node be the child of start node with index equal to
- // start offset."
- var referenceNode = startNode.childNodes[startOffset];
-
- // "If reference node is a block node or an Element with no children,
- // or is neither an Element nor a Text node, break from this loop."
- if (isBlockNode(referenceNode)
- || (referenceNode.nodeType == Node.ELEMENT_NODE
- && !referenceNode.hasChildNodes())
- || (referenceNode.nodeType != Node.ELEMENT_NODE
- && referenceNode.nodeType != Node.TEXT_NODE)) {
- break;
- }
-
- // "Set start node to reference node and start offset to 0."
- startNode = referenceNode;
- startOffset = 0;
- }
-
- // "While end node has at least one child:"
- while (endNode.hasChildNodes()) {
- // "If end offset is 0, and end node's parent is in the same editing
- // host, and end node is an inline node, set end offset to the index of
- // end node, then set end node to its parent and continue this loop
- // from the beginning."
- if (endOffset == 0
- && inSameEditingHost(endNode, endNode.parentNode)
- && isInlineNode(endNode)) {
- endOffset = getNodeIndex(endNode);
- endNode = endNode.parentNode;
- continue;
- }
-
- // "If end offset is 0, break from this loop."
- if (endOffset == 0) {
- break;
- }
-
- // "Let reference node be the child of end node with index equal to end
- // offset minus one."
- var referenceNode = endNode.childNodes[endOffset - 1];
-
- // "If reference node is a block node or an Element with no children,
- // or is neither an Element nor a Text node, break from this loop."
- if (isBlockNode(referenceNode)
- || (referenceNode.nodeType == Node.ELEMENT_NODE
- && !referenceNode.hasChildNodes())
- || (referenceNode.nodeType != Node.ELEMENT_NODE
- && referenceNode.nodeType != Node.TEXT_NODE)) {
- break;
- }
-
- // "Set end node to reference node and end offset to the length of
- // reference node."
- endNode = referenceNode;
- endOffset = getNodeLength(referenceNode);
+ // "While the next equivalent point for (start node, start offset) is not
+ // null, and that boundary point's node is in the same editing host and in
+ // the same block as start node, set (start node, start offset) to its next
+ // equivalent point."
+ while (getNextEquivalentPoint(startNode, startOffset)
+ && inSameEditingHost(getNextEquivalentPoint(startNode, startOffset)[0], startNode)
+ && inSameBlock(getNextEquivalentPoint(startNode, startOffset)[0], startNode)) {
+ var next = getNextEquivalentPoint(startNode, startOffset);
+ startNode = next[0];
+ startOffset = next[1];
+ }
+
+ // "While the previous equivalent point for (end node, end offset) is not
+ // null, and that boundary point's node is in the same editing host and in
+ // the same block as end node, set (end node, end offset) to its previous
+ // equivalent point."
+ while (getPreviousEquivalentPoint(endNode, endOffset)
+ && inSameEditingHost(getPreviousEquivalentPoint(endNode, endOffset)[0], endNode)
+ && inSameBlock(getPreviousEquivalentPoint(endNode, endOffset)[0], endNode)) {
+ var prev = getPreviousEquivalentPoint(endNode, endOffset);
+ endNode = prev[0];
+ endOffset = prev[1];
}
// "If (end node, end offset) is not after (start node, start offset):"
--- a/preprocess Thu Oct 27 15:16:47 2011 -0600
+++ b/preprocess Mon Nov 07 16:01:08 2011 -0700
@@ -168,19 +168,21 @@
# {{html|<foo>}} -> <foo>, no wrapper. Also, to avoid things like
# {{code|<b>}} messing up syntax highlighting, {{html<|b>}} is also supported
-# with the same effect as {{html|<b>}}.
+# with the same effect as {{html|<b>}}. Trailing whitespace before the }} will
+# be ignored, so that {{html|} }} will produce "}", since {{html|}}} won't work
+# (the first two "}"'s will terminate the expression).
def htmlsub(matchobj):
return matchobj.group(1).replace("<", "<") \
+ matchobj.group(2).replace("&", "&").replace("<", "<")
-# {{code|...}} is just a shortcut for <code title>{{pre|...}}</code>.
+# {{code|...}} is just a shortcut for <code title>{{html|...}}</code>.
def codesub(matchobj):
return "<code title>" \
+ htmlsub(matchobj) \
+ "</code>"
-s = re.compile(r"\{\{html(<?)\|(.*?)\}\}", re.DOTALL).sub(htmlsub, s)
-s = re.compile(r"\{\{code(<?)\|(.*?)\}\}", re.DOTALL).sub(codesub, s)
+s = re.compile(r"\{\{html(<?)\|(.*?)\s*\}\}", re.DOTALL).sub(htmlsub, s)
+s = re.compile(r"\{\{code(<?)\|(.*?)\s*\}\}", re.DOTALL).sub(codesub, s)
s = s.replace("<var>", "<var title>")
--- a/source.html Thu Oct 27 15:16:47 2011 -0600
+++ b/source.html Mon Nov 07 16:01:08 2011 -0700
@@ -5209,6 +5209,105 @@
<!--@}-->
<h3>Deleting the selection</h3>
<!-- @{ -->
+<p class=note>Sometimes one location corresponds to multiple distinct boundary
+points. For instance, in the DOM {{code|<p>Hello</p>}}, a boundary point might
+lie at the beginning of the text node or the beginning of the element node, but
+these don't logically differ much and will appear the same to the user, so we
+often want to treat them the same. The algorithms here (currently used only
+for the delete algorithm) allow navigating through such equivalent boundary
+points, for when we want to make the selection as inclusive or exclusive as
+possible. For deletion, we want to delete as few nodes as possible, so we move
+the start node forward and the end node backward. In other cases we might do
+the reverse, expanding the selection.
+
+<p>Given a [[boundarypoint]] (<var>node</var>, <var>offset</var>), the
+<dfn>next equivalent point</dfn> is either a [[boundarypoint]] or null, as
+returned by the following algorithm:
+
+<ol>
+ <li>If <var>node</var>'s [[length]] is zero, return null.
+
+ <li class=note>We don't want to move into or out of zero-length nodes,
+ because that would move us straight through them. For instance, if
+ {{code|{}<span></span>}} were equivalent to {{code|<span>{}</span>}}, it
+ would also be equivalent to {{code|<span></span>{} }}. This produces very
+ unexpected results for nodes like {{code|<br>}}.
+
+ <li>If <var>offset</var> is <var>node</var>'s [[length]], and
+ <var>node</var>'s [[parent]] is not null, return (<var>node</var>'s
+ [[parent]], 1 + <var>node</var>'s [[index]]).
+
+ <li class=note>For instance, {{code|<span>foo[]</span>}} is equivalent to
+ {{code|<span>foo{}</span>}}, which is equivalent to {{code|<span>foo</span>{}
+ }}.
+
+ <li>If <var>node</var> has a [[child]] with [[index]] <var>offset</var>,
+ and that [[child]]'s [[length]] is not zero, return (that [[child]], 0).
+
+ <li class=note>For instance, {{code|{}<span>foo</span>}} is equivalent to
+ {{code|<span>{}foo</span>}}, which is equivalent to
+ {{code|<span>[]foo</span>}}. As noted before, though, we don't descend into
+ empty nodes.
+
+ <li>Return null.
+</ol>
+
+<p>Given a [[boundarypoint]] (<var>node</var>, <var>offset</var>), the
+<dfn>previous equivalent point</dfn> is either a [[boundarypoint]] or null, as
+returned by the following algorithm:
+
+<ol>
+ <li>If <var>node</var>'s [[length]] is zero, return null.
+
+ <li>If <var>offset</var> is 0, and <var>node</var>'s [[parent]] is not null,
+ return (<var>node</var>'s [[parent]], <var>node</var>'s [[index]]).
+
+ <li>If <var>node</var> has a [[child]] with [[index]] <var>offset</var>
+ − 1, and that [[child]]'s [[length]] is not zero, return (that
+ [[child]], that [[child]]'s [[length]]).
+
+ <li>Return null.
+</ol>
+
+<!-- Turned out not to be necessary here, but I've left it in case it turns out
+to be handy elsewhere. -->
+<!--
+<p>Two [[boundarypoints]] <var>A</var> and <var>B</var> are <dfn>equivalent
+boundary points</dfn> if the following algorithm returns true:
+
+<p class=note>This is indeed an <a
+href=http://en.wikipedia.org/wiki/Equivalence_relation>equivalence
+relation</a>. Reflexivity, symmetry, and transitivity are all fairly obvious
+from the definition. It also isn't hard to check that using <span>next
+equivalent point</span> instead of <span>previous equivalent point</span> would
+yield the same definition, because the two algorithms are inverses if neither
+output is null.
+
+<ol>
+ <li>While <var>A</var>'s <span>previous equivalent point</span> is not null,
+ set <var>A</var> to its <span>previous equivalent point</span>.
+
+ <li>While <var>B</var>'s <span>previous equivalent point</span> is not null,
+ set <var>B</var> to its <span>previous equivalent point</span>.
+
+ <li>Return true if <var>A</var> is the same as <var>B</var>, otherwise false.
+</ol>
+-->
+
+<p>The <dfn>block node of</dfn> a [[node]] <var>node</var> is either a
+<span>block node</span> or null, as returned by the following algorithm:
+
+<ol>
+ <li>While <var>node</var> is an <span>inline node</span>, set <var>node</var>
+ to its [[parent]].
+
+ <li>Return <var>node</var>.
+</ol>
+
+<p>Two [[nodes]] are <dfn>in the same block</dfn> if the <span>block node
+of</span> the first is non-null and the same as the <span>block node of</span>
+the second.
+
<p class=comments>TODO: Consider what should happen for block merging in corner
cases like display: inline-table.
@@ -5268,105 +5367,21 @@
and <var>end offset</var> be the <span>active range</span>'s [[rangestart]]
and [[rangeend]] [[nodes]] and [[bpoffsets]].
- <li class=note>
- <p>First we adjust the selection so that we don't delete more than we should.
-
- <li>
- <div class=comments>
- <p>We don't want to keep going when we hit an element with no children,
- because then we'd do something like
-
-<pre>
-foo{<br />bar]
--> foo<br>{</br>bar]
--> foo<br />{bar]
--> foo<br />[bar]</pre>
-
- <p>and we deselected the {{code|<br>}}.
- </div>
-
- <p>While <var>start node</var> has at least one [[child]]:
-
- <ol>
- <li>
- <div class=comments>
- <p>For instance:
-
-<pre><b>foo[</b><i>bar]</i>
--> <b>foo{</b><i>bar]</i>
--> <b>foo</b>{<i>bar]</i></pre>
-
- <p>Then the next step will make it {{code|<b>foo</b><i>[bar]</i>}}.
-
- <p>We don't want to do this for block nodes, because that would lead to
- something like
-
- <pre><p>foo[</p><p>]bar<p></pre>
-
- <p>ultimately collapsing, which is wrong. Once we do the deletion, it
- needs to wind up {{code|<p>foo[]bar</p>}}, whereas an actually collapsed
- selection should do nothing.
- </div>
-
- <p>If <var>start offset</var> is <var>start node</var>'s [[length]], and
- <var>start node</var>'s [[parent]] is <span>in the same editing
- host</span>, and <var>start node</var> is an <span>inline node</span>, set
- <var>start offset</var> to one plus the [[index]] of <var>start node</var>,
- then set <var>start node</var> to its [[parent]] and continue this loop
- from the beginning.
-
- <li>
- <p class=comments>This happens if the first step brought us all the way up
- to the root. The step immediately after this loop will bring us back down
- again.
-
- <p>If <var>start offset</var> is <var>start node</var>'s [[length]],
- break from this loop.
-
- <li>Let <var>reference node</var> be the [[child]] of <var>start node</var>
- with [[index]] equal to <var>start offset</var>.
-
- <li>
- <div class=comments>
- <p>Don't descend into an element with no children, since then it won't get
- deleted even if it's selected. Don't descend into a block node, because
- then we might wind up not merging blocks when we should, e.g.
-
-<pre>foo{<p>}bar</p>
--> foo<p>{}bar</p></pre>
-
- <p>and nothing gets changed.
- </div>
-
- <p>If <var>reference node</var> is a <span>block node</span> or an
- [[element]] with no [[children]], or is neither an [[element]] nor a
- [[text]] node, break from this loop.
-
- <li>Set <var>start node</var> to <var>reference node</var> and <var>start
- offset</var> to 0.
- </ol>
-
- <li>While <var>end node</var> has at least one [[child]]:
-
- <ol>
- <li>If <var>end offset</var> is 0, and <var>end node</var>'s
- [[parent]] is <span>in the same editing host</span>, and <var>end
- node</var> is an <span>inline node</span>, set <var>end offset</var> to the
- [[index]] of <var>end node</var>, then set <var>end node</var> to its
- [[parent]] and continue this loop from the beginning.
-
- <li>If <var>end offset</var> is 0, break from this loop.
-
- <li>Let <var>reference node</var> be the [[child]] of <var>end node</var>
- with [[index]] equal to <var>end offset</var> minus one.
-
- <li>If <var>reference node</var> is a <span>block node</span> or an
- [[element]] with no [[children]], or is neither an [[element]] nor a
- [[text]] node, break from this loop.
-
- <li>Set <var>end node</var> to <var>reference node</var> and <var>end
- offset</var> to the [[length]] of <var>reference node</var>.
- </ol>
+ <li>While the <span>next equivalent point</span> for (<var>start node</var>,
+ <var>start offset</var>) is not null, and that [[boundarypoint]]'s [[node]]
+ is <span>in the same editing host</span> and <span>in the same block</span>
+ as <var>start node</var>, set (<var>start node</var>, <var>start
+ offset</var>) to its <span>next equivalent point</span>.
+
+ <li class=note>We don't want to leave the current block because otherwise,
+ for instance, a selection of {{code|<p>foo[</p><p>]bar</p>}} would be treated
+ as empty and nothing would be deleted, when we want it to merge the blocks.
+
+ <li>While the <span>previous equivalent point</span> for (<var>end
+ node</var>, <var>end offset</var>) is not null, and that [[boundarypoint]]'s
+ [[node]] is <span>in the same editing host</span> and <span>in the same
+ block</span> as <var>end node</var>, set (<var>end node</var>, <var>end
+ offset</var>) to its <span>previous equivalent point</span>.
<li>If (<var>end node</var>, <var>end offset</var>) is not [[bpafter]]
(<var>start node</var>, <var>start offset</var>):