--- a/editing.html Wed Aug 17 11:54:34 2011 -0600
+++ b/editing.html Wed Aug 17 14:36:07 2011 -0600
@@ -941,7 +941,9 @@
paragraph child" is conceptually similar to "block node", but based on the
element name. Generally we want to use block/inline node when we're interested
in the visual effect, and prohibited paragraph children when we're concerned
-about parsing or semantics.
+about parsing or semantics. TODO: Audit all "block node" usages to see if they
+need to become "visible block node", now that block nodes can be invisible (if
+they descend from display: none).
<p>A <dfn id=block-node>block node</dfn> is either 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> whose "display" property
does not have <a href=http://dev.w3.org/csswg/cssom/#resolved-value>resolved value</a> "inline" or "inline-block" or "inline-table" or
@@ -1006,10 +1008,107 @@
and seeing what happens. (Actually, setting display: none, so that it doesn't
mess up ranges.)
+<p>A <dfn id=whitespace-node>whitespace node</dfn> is either 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 whose <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> is
+the empty string; or 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 whose <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> consists only of one or
+more tabs (0x0009), line feeds (0x000A), carriage returns (0x000D), and/or
+spaces (0x0020), and whose <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> is 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> whose <a href=http://dev.w3.org/csswg/cssom/#resolved-value>resolved value</a> for
+"white-space" is "normal" or "nowrap"; or 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 whose <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code>
+consists only of one or more tabs (0x0009), carriage returns (0x000D), and/or
+spaces (0x0020), and whose <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> is 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> whose <a href=http://dev.w3.org/csswg/cssom/#resolved-value>resolved value</a> for
+"white-space" is "pre-line".
+
+<p><var title="">node</var> is a <dfn id=collapsed-whitespace-node>collapsed whitespace node</dfn> if the following
+algorithm returns true:
+
+<p class=XXX>This definition is also bad. It's a crude attempt to emulate
+CSS2.1 16.6.1, but leaving out a ton of the subtleties. I actually don't want
+the exact CSS definitions, because those depend on things like where lines are
+broken, but I'm not sure this definition is right anyway. E.g., what about a
+pre-line text node consisting of a single line break that's at the end of a
+block? That collapses, same idea as an extraneous line break. We could also
+worry about nodes containing only zwsp or such if we wanted, or display: none,
+or . . .
+
+<ol>
+ <li>If <var title="">node</var> is not a <a href=#whitespace-node>whitespace node</a>, return false.
+
+ <li>If <var title="">node</var>'s <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> is the empty string, return true.
+
+ <li>Let <var title="">ancestor</var> be <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-parent title=concept-tree-parent>parent</a>.
+
+ <li>If <var title="">ancestor</var> is null, return true.
+
+ <li>If the "display" property of some <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="">node</var> has
+ <a href=http://dev.w3.org/csswg/cssom/#resolved-value>resolved value</a> "none", return true.
+
+ <li>While <var title="">ancestor</var> is not a <a href=#block-node>block node</a> and 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> is not null, set <var title="">ancestor</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>
+ <div class=comments>
+ <p>At this point we know <var title="">node</var> consists of some whitespace, of a
+ sort that will collapse if it's at the start or end of a line. We go
+ backwards until we find the first block boundary, and if everything until
+ there is invisible or whitespace, we conclude that <var title="">node</var> is
+ collapsed. We assume a block boundary is either when we hit a line break or
+ block node, or we hit the end of <var title="">ancestor</var> (which is the nearest
+ ancestor block node). All this is very imprecise, of course, but it's fairly
+ simple and will work in common cases.
+
+ <p>We have to avoid invoking the definition of "visible" here to avoid
+ infinite recursion: that depends on the concept of collapsed whitespace
+ nodes. Instead, we repeat the parts we need, which turns out to be "not
+ much of it".
+ </div>
+
+ <p>Let <var title="">reference</var> be <var title="">node</var>.
+
+ <li>While <var title="">reference</var> is 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="">ancestor</var>:
+
+ <ol>
+ <li>Let <var title="">reference</var> be 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> before it 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>.
+
+ <li>If <var title="">reference</var> is a <a href=#block-node>block node</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>,
+ return true.
+
+ <li>If <var title="">reference</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 that is not a
+ <a href=#whitespace-node>whitespace node</a>, or is 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>, break from this loop.
+ </ol>
+
+ <li><p class=comments>We found something before our text node on (probably)
+ the same line, so presumably it's not at the line's start. Now we need to
+ look forward and see if we're at the line's end. If we aren't there either,
+ then we assume we're not collapsed, so return false.
+
+ <p>Let <var title="">reference</var> be <var title="">node</var>.
+
+ <li>While <var title="">reference</var> is 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="">ancestor</var>:
+
+ <ol>
+ <li>Let <var title="">reference</var> be 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> after it 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>, or
+ null if there is no such <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>.
+
+ <li>If <var title="">reference</var> is a <a href=#block-node>block node</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>,
+ return true.
+
+ <li>If <var title="">reference</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 that is not a
+ <a href=#whitespace-node>whitespace node</a>, or is 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>, break from this loop.
+ </ol>
+
+ <li>Return false.
+</ol>
+
+<p class=comments>TODO: Consider whether we really want to depend on img
+specifically here. It seems more likely that we want something like "any
+replaced content that has nonzero height and width" or such. When fixing this,
+make sure to audit for other occurrences of this assumption.
+
<p>Something is <dfn id=visible>visible</dfn> if it is 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> that either is a
-<a href=#block-node>block node</a>, or 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 whose <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code> is not empty, 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>, 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> that is not an <a href=#extraneous-line-break>extraneous line break</a>, 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> with a <a href=#visible>visible</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>.
+<a href=#block-node>block node</a>, or 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 that is not a <a href=#collapsed-whitespace-node>collapsed
+whitespace node</a>, 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>, 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> that is not an
+<a href=#extraneous-line-break>extraneous line break</a>, 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> with a <a href=#visible>visible</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>; excluding 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> with an <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container title="ancestor container">ancestor container</a>
+<code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> whose "display" property has <a href=http://dev.w3.org/csswg/cssom/#resolved-value>resolved value</a> "none".
<p>Something is <dfn id=invisible>invisible</dfn> if it is 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> that is not
<a href=#visible>visible</a>.
@@ -4010,6 +4109,21 @@
<h3 id=block-extending-a-range>Block-extending a range</h3>
+<p>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> (<var title="">node</var>, <var title="">offset</var>) is a <dfn id=block-start-point>block
+start point</dfn> if either <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-parent title=concept-tree-parent>parent</a> is null and
+<var title="">offset</var> is zero; or <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 either a
+<a href=#visible>visible</a> <a href=#block-node>block node</a> or a <a href=#visible>visible</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>.
+
+<p>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> (<var title="">node</var>, <var title="">offset</var>) is a <dfn id=block-end-point>block end
+point</dfn> if either <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-parent title=concept-tree-parent>parent</a> is null and
+<var title="">offset</var> is <var title="">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>; or <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>, 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
+<a href=#visible>visible</a> <a href=#block-node>block node</a>.
+
+<p>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> is a <dfn id=block-boundary-point>block boundary point</dfn> if it is either a
+<a href=#block-start-point>block start point</a> or a <a href=#block-end-point>block end point</a>.
+
<p>When a user agent is to <dfn id=block-extend>block-extend</dfn> a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>
<var title="">range</var>, it must run the following steps:
@@ -4039,77 +4153,47 @@
<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 the last such <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> 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>, and set <var title="">start node</var> to that <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 <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>Repeat the following steps:
+ <li>If (<var title="">start node</var>, <var title="">start offset</var>) is not a <a href=#block-start-point>block
+ start point</a>, repeat the following steps:
<ol>
- <li>If <var title="">start 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> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node or
- <var title="">start offset</var> is 0, 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>.
-
- <li>Otherwise, if <var title="">start 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>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 is <a href=#invisible>invisible</a>, subtract one
- from <var title="">start offset</var>.
-
- <li>
- <p class=comments>So if you have a collapsed selection at the end of a
- block, for instance, it will extend backwards into a block.
-
- <p>Otherwise, if <var title="">start node</var> has no <a href=#visible>visible</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>children</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> greater than or equal to <var title="">start
- offset</var> and <var title="">start node</var>'s last <a href=#visible>visible</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> is an <a href=#inline-node>inline node</a> that's not 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>, subtract one
- from <var title="">start offset</var>.
-
- <li>
- <p class=comments>IE also includes <br> (at least for the purposes of
- the indent command), but this is unlikely to match user expectations.
-
- <p>Otherwise, if <var title="">start node</var> has a <a href=#visible>visible</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> greater than or equal to <var title="">start offset</var>,
- and the first such <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=#inline-node>inline node</a>, and <var title="">start
- 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>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 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>, subtract one from
- <var title="">start offset</var>.
-
- <li>Otherwise, break from this loop.
+ <li>If <var title="">start offset</var> is zero, set it to <var title="">start node</var>'s
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>, 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>.
+
+ <li>Otherwise, subtract one from <var title="">start offset</var>.
+
+ <li>If (<var title="">start node</var>, <var title="">start offset</var>) is a <a href=#block-boundary-point>block
+ boundary point</a>, break from this loop.
</ol>
+ <li><p class=comments>This just changes something like <code title=""><div>{<p>foo]</p></div></code> to <code title="">{<div><p>foo]</p></div></code>.
+
+ <p>While <var title="">start offset</var> is zero and <var title="">start node</var>'s
+ <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> is not null, 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-indexof title=concept-indexof>index</a>, 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>.
+
<li>If some <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container title="ancestor container">ancestor container</a> of <var title="">end 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>, set
<var title="">end offset</var> to one plus the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> of the last such <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> 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>, and set <var title="">end node</var> to that <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 <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>Repeat the following steps:
+ <li>If (<var title="">end node</var>, <var title="">end offset</var>) is not a <a href=#block-end-point>block end
+ point</a>, repeat the following steps:
<ol>
- <li>If <var title="">end 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> or <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#comment>Comment</a></code> node or <var title="">end
- offset</var> is equal 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="">end node</var>, set
- <var title="">end offset</var> to one plus 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="">end node</var> and
- then set <var title="">end 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="">end 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>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="">end
- offset</var> is <a href=#invisible>invisible</a>, add one to <var title="">end offset</var>.
-
- <li>Otherwise, if <var title="">end node</var> has no <a href=#visible>visible</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>children</a> with <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a> less than <var title="">end offset</var> and <var title="">end
- node</var>'s first <a href=#visible>visible</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> is an <a href=#inline-node>inline
- node</a> that's not 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>, add one to <var title="">end offset</var>.
-
- <li>Otherwise, if <var title="">end node</var> has a <a href=#visible>visible</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> less than <var title="">end offset</var>, and the last such <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=#inline-node>inline node</a>, and <var title="">end 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>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="">end offset</var> 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>, add one to <var title="">end offset</var>.
-
- <li>Otherwise, break from this loop.
+ <li>If <var title="">end offset</var> is <var title="">end 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>, set it to
+ one plus <var title="">end node</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>, then set <var title="">end 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, add one to <var title="">end offset</var>.
+
+ <li>If (<var title="">end node</var>, <var title="">end offset</var>) is a <a href=#block-boundary-point>block
+ boundary point</a>, break from this loop.
</ol>
- <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="">end 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="">end
- offset</var> 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>, add one to <var title="">end offset</var>.
-
- <li>While <var title="">end offset</var> is equal 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="">end
- node</var>, set <var title="">end offset</var> to one plus 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="">end
- node</var> and then set <var title="">end 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>While <var title="">end offset</var> is <var title="">end 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> and
+ <var title="">end node</var>'s <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> is not null, set <var title="">end offset</var> to
+ one plus <var title="">end node</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>, then set <var title="">end 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>Let <var title="">new range</var> be a new <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> <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>nodes</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>offsets</a> are <var title="">start node</var>,
@@ -4124,37 +4208,47 @@
<ol>
<li>Let <var title="">offset</var> be zero.
- <li>While <var title="">offset</var> is zero, 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> and 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>Let <var title="">range</var> be a <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> 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>).
-
- <li><a href=#block-extend>Block-extend</a> <var title="">range</var>, and let <var title="">new range</var>
- be the result.
-
- <li>Return false if <var title="">new range</var>'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> is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-bp-before title=concept-bp-before>before</a>
- (<var title="">node</var>, <var title="">offset</var>), true otherwise.
+ <li>While (<var title="">node</var>, <var title="">offset</var>) is not a <a href=#block-boundary-point>block boundary
+ point</a>:
+
+ <ol>
+ <li>If <var title="">node</var> has a <a href=#visible>visible</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> minus one, return false.
+
+ <li>If <var title="">offset</var> is zero or <var title="">node</var> has no <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>,
+ set <var title="">offset</var> to <var title="">node</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>, 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, 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-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> minus one, then set <var title="">offset</var> to
+ <var title="">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>.
+ </ol>
+
+ <li>Return true.
</ol>
<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> <var title="">node</var> <dfn id=precedes-a-line-break>precedes a line break</dfn> if the following
algorithm returns true:
<ol>
- <li>Let <var title="">offset</var> be 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>While <var title="">offset</var> is 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>, set
- <var title="">offset</var> to one plus 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> and 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>Let <var title="">range</var> be a <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> 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>).
-
- <li><a href=#block-extend>Block-extend</a> <var title="">range</var>, and let <var title="">new range</var>
- be the result.
-
- <li>Return false if <var title="">new range</var>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a> is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-bp-after title=concept-bp-after>after</a>
- (<var title="">node</var>, <var title="">offset</var>), true otherwise.
+ <li>Let <var title="">offset</var> be <var title="">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>.
+
+ <li>While (<var title="">node</var>, <var title="">offset</var>) is not a <a href=#block-boundary-point>block boundary
+ point</a>:
+
+ <ol>
+ <li>If <var title="">node</var> has a <a href=#visible>visible</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>, return false.
+
+ <li>If <var title="">offset</var> is <var title="">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> or <var title="">node</var>
+ has no <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>, set <var title="">offset</var> to one plus <var title="">node</var>'s
+ <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-indexof title=concept-indexof>index</a>, 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, 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-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> and set <var title="">offset</var> to zero.
+ </ol>
+
+ <li>Return true.
</ol>
--- a/implementation.js Wed Aug 17 11:54:34 2011 -0600
+++ b/implementation.js Wed Aug 17 14:36:07 2011 -0600
@@ -51,20 +51,27 @@
* Returns true if ancestor is an ancestor of descendant, false otherwise.
*/
function isAncestor(ancestor, descendant) {
- if (!ancestor || !descendant) {
- return false;
- }
- while (descendant && descendant != ancestor) {
- descendant = descendant.parentNode;
- }
- return descendant == ancestor;
+ return ancestor
+ && descendant
+ && Boolean(ancestor.compareDocumentPosition(descendant) & Node.DOCUMENT_POSITION_CONTAINED_BY);
+}
+
+/**
+ * Returns true if ancestor is an ancestor of or equal to descendant, false
+ * otherwise.
+ */
+function isAncestorContainer(ancestor, descendant) {
+ return (ancestor || descendant)
+ && (ancestor == descendant || isAncestor(ancestor, descendant));
}
/**
* Returns true if descendant is a descendant of ancestor, false otherwise.
*/
function isDescendant(descendant, ancestor) {
- return isAncestor(ancestor, descendant);
+ return ancestor
+ && descendant
+ && Boolean(ancestor.compareDocumentPosition(descendant) & Node.DOCUMENT_POSITION_CONTAINED_BY);
}
/**
@@ -886,24 +893,146 @@
return origHeight == finalHeight;
}
+// "A whitespace node is either a Text node whose data is the empty string; or
+// a Text node whose data consists only of one or more tabs (0x0009), line
+// feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
+// parent is an Element whose resolved value for "white-space" is "normal" or
+// "nowrap"; or a Text node whose data consists only of one or more tabs
+// (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
+// parent is an Element whose resolved value for "white-space" is "pre-line"."
+function isWhitespaceNode(node) {
+ return node
+ && node.nodeType == Node.TEXT_NODE
+ && (node.data == ""
+ || (
+ /^[\t\n\r ]+$/.test(node.data)
+ && node.parentNode
+ && node.parentNode.nodeType == Node.ELEMENT_NODE
+ && ["normal", "nowrap"].indexOf(getComputedStyle(node.parentNode).whiteSpace) != -1
+ ) || (
+ /^[\t\r ]+$/.test(node.data)
+ && node.parentNode
+ && node.parentNode.nodeType == Node.ELEMENT_NODE
+ && getComputedStyle(node.parentNode).whiteSpace == "pre-line"
+ ));
+}
+
+// "node is a collapsed whitespace node if the following algorithm returns
+// true:"
+function isCollapsedWhitespaceNode(node) {
+ // "If node is not a whitespace node, return false."
+ if (!isWhitespaceNode(node)) {
+ return false;
+ }
+
+ // "If node's data is the empty string, return true."
+ if (node.data == "") {
+ return true;
+ }
+
+ // "Let ancestor be node's parent."
+ var ancestor = node.parentNode;
+
+ // "If ancestor is null, return true."
+ if (!ancestor) {
+ return true;
+ }
+
+ // "If the "display" property of some ancestor of node has resolved value
+ // "none", return true."
+ if (getAncestors(node).some(function(ancestor) {
+ return ancestor.nodeType == Node.ELEMENT_NODE
+ && getComputedStyle(ancestor).display == "none";
+ })) {
+ return true;
+ }
+
+ // "While ancestor is not a block node and its parent is not null, set
+ // ancestor to its parent."
+ while (!isBlockNode(ancestor)
+ && ancestor.parentNode) {
+ ancestor = ancestor.parentNode;
+ }
+
+ // "Let reference be node."
+ var reference = node;
+
+ // "While reference is a descendant of ancestor:"
+ while (reference != ancestor) {
+ // "Let reference be the node before it in tree order."
+ reference = previousNode(reference);
+
+ // "If reference is a block node or a br, return true."
+ if (isBlockNode(reference)
+ || isHtmlElement(reference, "br")) {
+ return true;
+ }
+
+ // "If reference is a Text node that is not a whitespace node, or is an
+ // img, break from this loop."
+ if ((reference.nodeType == Node.TEXT_NODE && !isWhitespaceNode(reference))
+ || isHtmlElement(reference, "img")) {
+ break;
+ }
+ }
+
+ // "Let reference be node."
+ reference = node;
+
+ // "While reference is a descendant of ancestor:"
+ var stop = nextNodeDescendants(ancestor);
+ while (reference != stop) {
+ // "Let reference be the node after it in tree order, or null if there
+ // is no such node."
+ reference = nextNode(reference);
+
+ // "If reference is a block node or a br, return true."
+ if (isBlockNode(reference)
+ || isHtmlElement(reference, "br")) {
+ return true;
+ }
+
+ // "If reference is a Text node that is not a whitespace node, or is an
+ // img, break from this loop."
+ if ((reference && reference.nodeType == Node.TEXT_NODE && !isWhitespaceNode(reference))
+ || isHtmlElement(reference, "img")) {
+ break;
+ }
+ }
+
+ // "Return false."
+ return false;
+}
+
// "Something is visible if it is a node that either is a block node, or a Text
-// node whose data is not empty, or an img, or a br that is not an extraneous
-// line break, or any node with a visible descendant."
+// node that is not a collapsed whitespace node, or an img, or a br that is not
+// an extraneous line break, or any node with a visible descendant; excluding
+// any node with an ancestor container Element whose "display" property has
+// resolved value "none"."
function isVisible(node) {
if (!node) {
return false;
}
+
+ if (getAncestors(node).concat(node)
+ .filter(function(node) { return node.nodeType == Node.ELEMENT_NODE })
+ .some(function(node) { return getComputedStyle(node).display == "none" })) {
+ return false;
+ }
+
if (isBlockNode(node)
- || (node.nodeType == Node.TEXT_NODE && node.length)
+ || (node.nodeType == Node.TEXT_NODE && !isCollapsedWhitespaceNode(node))
|| isHtmlElement(node, "img")
|| (isHtmlElement(node, "br") && !isExtraneousLineBreak(node))) {
return true;
}
+
for (var i = 0; i < node.childNodes.length; i++) {
if (isVisible(node.childNodes[i])) {
return true;
}
}
+
return false;
}
@@ -3844,6 +3973,35 @@
///// Block-extending a range /////
//@{
+// "A boundary point (node, offset) is a block start point if either node's
+// parent is null and offset is zero; or node has a child with index offset −
+// 1, and that child is either a visible block node or a visible br."
+function isBlockStartPoint(node, offset) {
+ return (!node.parentNode && offset == 0)
+ || (0 <= offset - 1
+ && offset - 1 < node.childNodes.length
+ && isVisible(node.childNodes[offset - 1])
+ && (isBlockNode(node.childNodes[offset - 1])
+ || isHtmlElement(node.childNodes[offset - 1], "br")));
+}
+
+// "A boundary point (node, offset) is a block end point if either node's
+// parent is null and offset is node's length; or node has a child with index
+// offset, and that child is a visible block node."
+function isBlockEndPoint(node, offset) {
+ return (!node.parentNode && offset == getNodeLength(node))
+ || (offset < node.childNodes.length
+ && isVisible(node.childNodes[offset])
+ && isBlockNode(node.childNodes[offset]));
+}
+
+// "A boundary point is a block boundary point if it is either a block start
+// point or a block end point."
+function isBlockBoundaryPoint(node, offset) {
+ return isBlockStartPoint(node, offset)
+ || isBlockEndPoint(node, offset);
+}
+
function blockExtend(range) {
// "Let start node, start offset, end node, and end offset be the start
// and end nodes and offsets of the range."
@@ -3863,48 +4021,30 @@
startNode = liAncestors[0].parentNode;
}
- // "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) {
+ // "If (start node, start offset) is not a block start point, repeat the
+ // following steps:"
+ if (!isBlockStartPoint(startNode, startOffset)) do {
+ // "If start offset is zero, set it to start node's index, then set
+ // start node to its parent."
+ if (startOffset == 0) {
startOffset = getNodeIndex(startNode);
startNode = startNode.parentNode;
- // "Otherwise, if start node's child with index start offset minus one
- // is invisible, subtract one from start offset."
- } else if (isInvisible(startNode.childNodes[startOffset - 1])) {
- startOffset--;
-
- // "Otherwise, if start node has no visible children with index greater
- // than or equal to start offset and start node's last visible child is
- // an inline node that's not a br, subtract one from start offset."
- } else if (![].slice.call(startNode.childNodes, startOffset).some(isVisible)
- && isInlineNode([].filter.call(startNode.childNodes, isVisible).slice(-1)[0])
- && !isHtmlElement([].filter.call(startNode.childNodes, isVisible).slice(-1)[0], "br")) {
+ // "Otherwise, subtract one from start offset."
+ } else {
startOffset--;
-
- // "Otherwise, if start node has a visible child with index greater
- // than or equal to start offset, and the first such child is an inline
- // node, and start node's child with index start offset minus one is an
- // inline node other than a br, subtract one from start offset."
- //
- // The functional programming here might be a bit heavy, but it would
- // be a pain to write it differently.
- } else if (isInlineNode([].filter.call(startNode.childNodes, function(child, i) {
- return isVisible(child) && i >= startOffset;
- })[0])
- && isInlineNode(startNode.childNodes[startOffset - 1])
- && !isHtmlElement(startNode.childNodes[startOffset - 1], "br")) {
- startOffset--;
-
- // "Otherwise, break from this loop."
- } else {
- break;
- }
+ }
+
+ // "If (start node, start offset) is a block boundary point, break from
+ // this loop."
+ } while (!isBlockBoundaryPoint(startNode, startOffset));
+
+ // "While start offset is zero and start node's parent is not null, set
+ // start offset to start node's index, then set start node to its parent."
+ while (startOffset == 0
+ && startNode.parentNode) {
+ startOffset = getNodeIndex(startNode);
+ startNode = startNode.parentNode;
}
// "If some ancestor container of end node is an li, set end offset to one
@@ -3918,56 +4058,29 @@
endNode = liAncestors[0].parentNode;
}
- // "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)) {
+ // "If (end node, end offset) is not a block end point, repeat the
+ // following steps:"
+ if (!isBlockEndPoint(endNode, endOffset)) do {
+ // "If end offset is end node's length, set it to one plus end node's
+ // index, then set end node to its parent."
+ if (endOffset == getNodeLength(endNode)) {
endOffset = 1 + getNodeIndex(endNode);
endNode = endNode.parentNode;
- // "Otherwise, if end node's child with index end offset is invisible,
- // add one to end offset."
- } else if (isInvisible(endNode.childNodes[endOffset])) {
- endOffset++;
-
- // "Otherwise, if end node has no visible children with index less than
- // end offset and end node's first visible child is an inline node
- // that's not a br, add one to end offset."
- } else if (![].slice.call(endNode.childNodes, 0, endOffset).some(isVisible)
- && isInlineNode([].filter.call(endNode.childNodes, isVisible)[0])
- && !isHtmlElement([].filter.call(endNode.childNodes, isVisible)[0], "br")) {
+ // "Otherwise, add one to end offset.
+ } else {
endOffset++;
-
- // "Otherwise, if end node has a visible child with index less than end
- // offset, and the last such child is an inline node, and end node's
- // child with index end offset is an inline node other than a br, add
- // one to end offset."
- } else if (isInlineNode([].filter.call(endNode.childNodes, function(child, i) {
- return isVisible(child) && i < endOffset;
- }).slice(-1)[0])
- && isInlineNode(endNode.childNodes[endOffset])
- && !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)) {
+ }
+
+ // "If (end node, end offset) is a block boundary point, break from
+ // this loop."
+ } while (!isBlockBoundaryPoint(endNode, endOffset));
+
+ // "While end offset is end node's length and end node's parent is not
+ // null, set end offset to one plus end node's index, then set end node to
+ // its parent."
+ while (endOffset == getNodeLength(endNode)
+ && endNode.parentNode) {
endOffset = 1 + getNodeIndex(endNode);
endNode = endNode.parentNode;
}
@@ -3986,46 +4099,64 @@
// "Let offset be zero."
var offset = 0;
- // "While offset is zero, set offset to the index of node and then set node
- // to its parent."
- while (offset == 0) {
- offset = getNodeIndex(node);
- node = node.parentNode;
- }
-
- // "Let range be a range with start and end (node, offset)."
- var range = document.createRange();
- range.setStart(node, offset);
-
- // "Block-extend range, and let new range be the result."
- var newRange = blockExtend(range);
-
- // "Return false if new range's start is before (node, offset), true
- // otherwise."
- return getPosition(newRange.startContainer, newRange.startOffset, node, offset) != "before";
+ // "While (node, offset) is not a block boundary point:"
+ while (!isBlockBoundaryPoint(node, offset)) {
+ // "If node has a visible child with index offset minus one, return
+ // false."
+ if (0 <= offset - 1
+ && offset - 1 < node.childNodes.length
+ && isVisible(node.childNodes[offset - 1])) {
+ return false;
+ }
+
+ // "If offset is zero or node has no children, set offset to node's
+ // index, then set node to its parent."
+ if (offset == 0
+ || !node.hasChildNodes()) {
+ offset = getNodeIndex(node);
+ node = node.parentNode;
+
+ // "Otherwise, set node to its child with index offset minus one, then
+ // set offset to node's length."
+ } else {
+ node = node.childNodes[offset - 1];
+ offset = getNodeLength(node);
+ }
+ }
+
+ // "Return true."
+ return true;
}
function precedesLineBreak(node) {
- // "Let offset be the length of node."
+ // "Let offset be node's length."
var offset = getNodeLength(node);
- // "While offset is the length of node, set offset to one plus the index of
- // node and then set node to its parent."
- while (offset == getNodeLength(node)) {
- offset = 1 + getNodeIndex(node);
- node = node.parentNode;
- }
-
- // "Let range be a range with start and end (node, offset)."
- var range = document.createRange();
- range.setStart(node, offset);
-
- // "Block-extend range, and let new range be the result."
- var newRange = blockExtend(range);
-
- // "Return false if new range's end is after (node, offset), true
- // otherwise."
- return getPosition(newRange.endContainer, newRange.endOffset, node, offset) != "after";
+ // "While (node, offset) is not a block boundary point:"
+ while (!isBlockBoundaryPoint(node, offset)) {
+ // "If node has a visible child with index offset, return false."
+ if (offset < node.childNodes.length
+ && isVisible(node.childNodes[offset])) {
+ return false;
+ }
+
+ // "If offset is node's length or node has no children, set offset to
+ // one plus node's index, then set node to its parent."
+ if (offset == getNodeLength(node)
+ || !node.hasChildNodes()) {
+ offset = 1 + getNodeIndex(node);
+ node = node.parentNode;
+
+ // "Otherwise, set node to its child with index offset and set offset
+ // to zero."
+ } else {
+ node = node.childNodes[offset];
+ offset = 0;
+ }
+ }
+
+ // "Return true."
+ return true;
}
//@}
--- a/source.html Wed Aug 17 11:54:34 2011 -0600
+++ b/source.html Wed Aug 17 14:36:07 2011 -0600
@@ -895,7 +895,9 @@
paragraph child" is conceptually similar to "block node", but based on the
element name. Generally we want to use block/inline node when we're interested
in the visual effect, and prohibited paragraph children when we're concerned
-about parsing or semantics.
+about parsing or semantics. TODO: Audit all "block node" usages to see if they
+need to become "visible block node", now that block nodes can be invisible (if
+they descend from display: none).
<p>A <dfn>block node</dfn> is either an [[element]] whose "display" property
does not have [[resval]] "inline" or "inline-block" or "inline-table" or
@@ -961,10 +963,107 @@
and seeing what happens. (Actually, setting display: none, so that it doesn't
mess up ranges.)
+<p>A <dfn>whitespace node</dfn> is either a [[text]] node whose [[cddata]] is
+the empty string; or a [[text]] node whose [[cddata]] consists only of one or
+more tabs (0x0009), line feeds (0x000A), carriage returns (0x000D), and/or
+spaces (0x0020), and whose [[parent]] is an [[element]] whose [[resval]] for
+"white-space" is "normal" or "nowrap"; or a [[text]] node whose [[cddata]]
+consists only of one or more tabs (0x0009), carriage returns (0x000D), and/or
+spaces (0x0020), and whose [[parent]] is an [[element]] whose [[resval]] for
+"white-space" is "pre-line".
+
+<p><var>node</var> is a <dfn>collapsed whitespace node</dfn> if the following
+algorithm returns true:
+
+<p class=XXX>This definition is also bad. It's a crude attempt to emulate
+CSS2.1 16.6.1, but leaving out a ton of the subtleties. I actually don't want
+the exact CSS definitions, because those depend on things like where lines are
+broken, but I'm not sure this definition is right anyway. E.g., what about a
+pre-line text node consisting of a single line break that's at the end of a
+block? That collapses, same idea as an extraneous line break. We could also
+worry about nodes containing only zwsp or such if we wanted, or display: none,
+or . . .
+
+<ol>
+ <li>If <var>node</var> is not a <span>whitespace node</span>, return false.
+
+ <li>If <var>node</var>'s [[cddata]] is the empty string, return true.
+
+ <li>Let <var>ancestor</var> be <var>node</var>'s [[parent]].
+
+ <li>If <var>ancestor</var> is null, return true.
+
+ <li>If the "display" property of some [[ancestor]] of <var>node</var> has
+ [[resval]] "none", return true.
+
+ <li>While <var>ancestor</var> is not a <span>block node</span> and its
+ [[parent]] is not null, set <var>ancestor</var> to its [[parent]].
+
+ <li>
+ <div class=comments>
+ <p>At this point we know <var>node</var> consists of some whitespace, of a
+ sort that will collapse if it's at the start or end of a line. We go
+ backwards until we find the first block boundary, and if everything until
+ there is invisible or whitespace, we conclude that <var>node</var> is
+ collapsed. We assume a block boundary is either when we hit a line break or
+ block node, or we hit the end of <var>ancestor</var> (which is the nearest
+ ancestor block node). All this is very imprecise, of course, but it's fairly
+ simple and will work in common cases.
+
+ <p>We have to avoid invoking the definition of "visible" here to avoid
+ infinite recursion: that depends on the concept of collapsed whitespace
+ nodes. Instead, we repeat the parts we need, which turns out to be "not
+ much of it".
+ </div>
+
+ <p>Let <var>reference</var> be <var>node</var>.
+
+ <li>While <var>reference</var> is a [[descendant]] of <var>ancestor</var>:
+
+ <ol>
+ <li>Let <var>reference</var> be the [[node]] before it in [[treeorder]].
+
+ <li>If <var>reference</var> is a <span>block node</span> or a [[br]],
+ return true.
+
+ <li>If <var>reference</var> is a [[text]] node that is not a
+ <span>whitespace node</span>, or is an [[img]], break from this loop.
+ </ol>
+
+ <li><p class=comments>We found something before our text node on (probably)
+ the same line, so presumably it's not at the line's start. Now we need to
+ look forward and see if we're at the line's end. If we aren't there either,
+ then we assume we're not collapsed, so return false.
+
+ <p>Let <var>reference</var> be <var>node</var>.
+
+ <li>While <var>reference</var> is a [[descendant]] of <var>ancestor</var>:
+
+ <ol>
+ <li>Let <var>reference</var> be the [[node]] after it in [[treeorder]], or
+ null if there is no such [[node]].
+
+ <li>If <var>reference</var> is a <span>block node</span> or a [[br]],
+ return true.
+
+ <li>If <var>reference</var> is a [[text]] node that is not a
+ <span>whitespace node</span>, or is an [[img]], break from this loop.
+ </ol>
+
+ <li>Return false.
+</ol>
+
+<p class=comments>TODO: Consider whether we really want to depend on img
+specifically here. It seems more likely that we want something like "any
+replaced content that has nonzero height and width" or such. When fixing this,
+make sure to audit for other occurrences of this assumption.
+
<p>Something is <dfn>visible</dfn> if it is a [[node]] that either is a
-<span>block node</span>, or a [[text]] node whose [[cddata]] is not empty, or
-an [[img]], or a [[br]] that is not an <span>extraneous line break</span>, or
-any [[node]] with a <span>visible</span> [[descendant]].
+<span>block node</span>, or a [[text]] node that is not a <span>collapsed
+whitespace node</span>, or an [[img]], or a [[br]] that is not an
+<span>extraneous line break</span>, or any [[node]] with a <span>visible</span>
+[[descendant]]; excluding any [[node]] with an [[ancestorcontainer]]
+[[element]] whose "display" property has [[resval]] "none".
<p>Something is <dfn>invisible</dfn> if it is a [[node]] that is not
<span>visible</span>.
@@ -4027,6 +4126,21 @@
<!-- @} -->
<h3>Block-extending a range</h3>
<!-- @{ -->
+<p>A [[boundarypoint]] (<var>node</var>, <var>offset</var>) is a <dfn>block
+start point</dfn> if either <var>node</var>'s [[parent]] is null and
+<var>offset</var> is zero; or <var>node</var> has a [[child]] with [[index]]
+<var>offset</var> − 1, and that [[child]] is either a
+<span>visible</span> <span>block node</span> or a <span>visible</span> [[br]].
+
+<p>A [[boundarypoint]] (<var>node</var>, <var>offset</var>) is a <dfn>block end
+point</dfn> if either <var>node</var>'s [[parent]] is null and
+<var>offset</var> is <var>node</var>'s [[length]]; or <var>node</var> has a
+[[child]] with [[index]] <var>offset</var>, and that [[child]] is a
+<span>visible</span> <span>block node</span>.
+
+<p>A [[boundarypoint]] is a <dfn>block boundary point</dfn> if it is either a
+<span>block start point</span> or a <span>block end point</span>.
+
<p>When a user agent is to <dfn>block-extend</dfn> a [[range]]
<var>range</var>, it must run the following steps:
@@ -4057,77 +4171,49 @@
<var>start offset</var> to the [[index]] of the last such [[li]] in
[[treeorder]], and set <var>start node</var> to that [[li]]'s [[parent]].
- <li>Repeat the following steps:
+ <li>If (<var>start node</var>, <var>start offset</var>) is not a <span>block
+ start point</span>, repeat the following steps:
<ol>
- <li>If <var>start node</var> is a [[text]] or [[comment]] node or
- <var>start offset</var> is 0, set <var>start offset</var> to the [[index]]
- of <var>start node</var> and then set <var>start node</var> to its
- [[parent]].
-
- <li>Otherwise, if <var>start node</var>'s [[child]] with [[index]]
- <var>start offset</var> minus one is <span>invisible</span>, subtract one
- from <var>start offset</var>.
-
- <li>
- <p class=comments>So if you have a collapsed selection at the end of a
- block, for instance, it will extend backwards into a block.
-
- <p>Otherwise, if <var>start node</var> has no <span>visible</span>
- [[children]] with [[index]] greater than or equal to <var>start
- offset</var> and <var>start node</var>'s last <span>visible</span>
- [[child]] is an <span>inline node</span> that's not a [[br]], subtract one
- from <var>start offset</var>.
-
- <li>
- <p class=comments>IE also includes <br> (at least for the purposes of
- the indent command), but this is unlikely to match user expectations.
-
- <p>Otherwise, if <var>start node</var> has a <span>visible</span>
- [[child]] with [[index]] greater than or equal to <var>start offset</var>,
- and the first such [[child]] is an <span>inline node</span>, and <var>start
- node</var>'s [[child]] with [[index]] <var>start offset</var> minus one is
- an <span>inline node</span> other than a [[br]], subtract one from
- <var>start offset</var>.
-
- <li>Otherwise, break from this loop.
+ <li>If <var>start offset</var> is zero, set it to <var>start node</var>'s
+ [[index]], then set <var>start node</var> to its [[parent]].
+
+ <li>Otherwise, subtract one from <var>start offset</var>.
+
+ <li>If (<var>start node</var>, <var>start offset</var>) is a <span>block
+ boundary point</span>, break from this loop.
</ol>
+ <li><p class=comments>This just changes something like <code
+ title><div>{<p>foo]</p></div></code> to <code
+ title>{<div><p>foo]</p></div></code>.
+
+ <p>While <var>start offset</var> is zero and <var>start node</var>'s
+ [[parent]] is not null, set <var>start offset</var> to <var>start
+ node</var>'s [[index]], then set <var>start node</var> to its [[parent]].
+
<li>If some [[ancestorcontainer]] of <var>end node</var> is an [[li]], set
<var>end offset</var> to one plus the [[index]] of the last such [[li]] in
[[treeorder]], and set <var>end node</var> to that [[li]]'s [[parent]].
- <li>Repeat the following steps:
+ <li>If (<var>end node</var>, <var>end offset</var>) is not a <span>block end
+ point</span>, repeat the following steps:
<ol>
- <li>If <var>end node</var> is a [[text]] or [[comment]] node or <var>end
- offset</var> is equal to the [[nodelength]] of <var>end node</var>, set
- <var>end offset</var> to one plus the [[index]] of <var>end node</var> and
- then set <var>end node</var> to its [[parent]].
-
- <li>Otherwise, if <var>end node</var>'s [[child]] with [[index]] <var>end
- offset</var> is <span>invisible</span>, add one to <var>end offset</var>.
-
- <li>Otherwise, if <var>end node</var> has no <span>visible</span>
- [[children]] with [[index]] less than <var>end offset</var> and <var>end
- node</var>'s first <span>visible</span> [[child]] is an <span>inline
- node</span> that's not a [[br]], add one to <var>end offset</var>.
-
- <li>Otherwise, if <var>end node</var> has a <span>visible</span> [[child]]
- with [[index]] less than <var>end offset</var>, and the last such [[child]]
- is an <span>inline node</span>, and <var>end node</var>'s [[child]] with
- [[index]] <var>end offset</var> is an <span>inline node</span> other than a
- [[br]], add one to <var>end offset</var>.
-
- <li>Otherwise, break from this loop.
+ <li>If <var>end offset</var> is <var>end node</var>'s [[length]], set it to
+ one plus <var>end node</var>'s [[index]], then set <var>end node</var> to
+ its [[parent]].
+
+ <li>Otherwise, add one to <var>end offset</var>.
+
+ <li>If (<var>end node</var>, <var>end offset</var>) is a <span>block
+ boundary point</span>, break from this loop.
</ol>
- <li>If the [[child]] of <var>end node</var> with [[index]] <var>end
- offset</var> is a [[br]], add one to <var>end offset</var>.
-
- <li>While <var>end offset</var> is equal to the [[nodelength]] of <var>end
- node</var>, set <var>end offset</var> to one plus the [[index]] of <var>end
- node</var> and then set <var>end node</var> to its [[parent]].
+ <li>While <var>end offset</var> is <var>end node</var>'s [[length]] and
+ <var>end node</var>'s [[parent]] is not null, set <var>end offset</var> to
+ one plus <var>end node</var>'s [[index]], then set <var>end node</var> to its
+ [[parent]].
<li>Let <var>new range</var> be a new [[range]] whose [[rangestart]] and
[[rangeend]] [[bpnodes]] and [[bpoffsets]] are <var>start node</var>,
@@ -4142,37 +4228,47 @@
<ol>
<li>Let <var>offset</var> be zero.
- <li>While <var>offset</var> is zero, set <var>offset</var> to the [[index]]
- of <var>node</var> and then set <var>node</var> to its [[parent]].
-
- <li>Let <var>range</var> be a [[range]] with [[rangestart]] and [[rangeend]]
- (<var>node</var>, <var>offset</var>).
-
- <li><span>Block-extend</span> <var>range</var>, and let <var>new range</var>
- be the result.
-
- <li>Return false if <var>new range</var>'s [[rangestart]] is [[bpbefore]]
- (<var>node</var>, <var>offset</var>), true otherwise.
+ <li>While (<var>node</var>, <var>offset</var>) is not a <span>block boundary
+ point</span>:
+
+ <ol>
+ <li>If <var>node</var> has a <span>visible</span> [[child]] with [[index]]
+ <var>offset</var> minus one, return false.
+
+ <li>If <var>offset</var> is zero or <var>node</var> has no [[children]],
+ set <var>offset</var> to <var>node</var>'s [[index]], then set
+ <var>node</var> to its [[parent]].
+
+ <li>Otherwise, set <var>node</var> to its [[child]] with [[index]]
+ <var>offset</var> minus one, then set <var>offset</var> to
+ <var>node</var>'s [[length]].
+ </ol>
+
+ <li>Return true.
</ol>
<p>A [[node]] <var>node</var> <dfn>precedes a line break</dfn> if the following
algorithm returns true:
<ol>
- <li>Let <var>offset</var> be the [[length]] of <var>node</var>.
-
- <li>While <var>offset</var> is the [[length]] of <var>node</var>, set
- <var>offset</var> to one plus the [[index]] of <var>node</var> and then set
- <var>node</var> to its [[parent]].
-
- <li>Let <var>range</var> be a [[range]] with [[rangestart]] and [[rangeend]]
- (<var>node</var>, <var>offset</var>).
-
- <li><span>Block-extend</span> <var>range</var>, and let <var>new range</var>
- be the result.
-
- <li>Return false if <var>new range</var>'s [[rangeend]] is [[bpafter]]
- (<var>node</var>, <var>offset</var>), true otherwise.
+ <li>Let <var>offset</var> be <var>node</var>'s [[length]].
+
+ <li>While (<var>node</var>, <var>offset</var>) is not a <span>block boundary
+ point</span>:
+
+ <ol>
+ <li>If <var>node</var> has a <span>visible</span> [[child]] with [[index]]
+ <var>offset</var>, return false.
+
+ <li>If <var>offset</var> is <var>node</var>'s [[length]] or <var>node</var>
+ has no [[children]], set <var>offset</var> to one plus <var>node</var>'s
+ [[index]], then set <var>node</var> to its [[parent]].
+
+ <li>Otherwise, set <var>node</var> to its [[child]] with [[index]]
+ <var>offset</var> and set <var>offset</var> to zero.
+ </ol>
+
+ <li>Return true.
</ol>
<!-- @} -->