Revamp visibility and block-extend
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Wed, 17 Aug 2011 14:36:07 -0600
changeset 535 6b6855ea679c
parent 534 11c5d9a0308f
child 536 7b22f518099b
Revamp visibility and block-extend

I started out intending only to change the definition of invisibility to
exclude display: none and collapsed whitespace nodes, but the definition
of collapsed whitespace nodes turned out to be complicated and
indirectly led to me rewriting the block-extend algorithm. The new
algorithms in the block-extend section are slightly shorter, but IMO a
lot more understandable.

Fixes: http://www.w3.org/Bugs/Public/show_bug.cgi?id=13631
Reported-By: Ryosuke Niwa
Report-URL: http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2011-August/032782.html
editing.html
implementation.js
source.html
--- 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> &minus; 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 &lt;br&gt; (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="">&lt;div&gt;{&lt;p&gt;foo]&lt;/p&gt;&lt;/div&gt;</code> to <code title="">{&lt;div&gt;&lt;p&gt;foo]&lt;/p&gt;&lt;/div&gt;</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> &minus; 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 &lt;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>&lt;div>{&lt;p>foo]&lt;/p>&lt;/div></code> to <code
+  title>{&lt;div>&lt;p>foo]&lt;/p>&lt;/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>
 
 <!-- @} -->