LayoutTests:
authorjusting <justing@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Jul 2006 02:13:04 +0000 (02:13 +0000)
committerjusting <justing@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Jul 2006 02:13:04 +0000 (02:13 +0000)
        Reviewed by levi

        <rdar://problem/4609184>
        Mail crashes at WebCore::Selection::adjustForEditableContent
        <rdar://problem/4609140>
        Crash at WebCore::Range::compareBoundaryPoints

        * editing/selection/mixed-editability-1-expected.checksum: Added.
        * editing/selection/mixed-editability-1-expected.png: Added.
        * editing/selection/mixed-editability-1-expected.txt: Added.
        * editing/selection/mixed-editability-1.html: Added.
        * editing/selection/mixed-editability-2-expected.checksum: Added.
        * editing/selection/mixed-editability-2-expected.png: Added.
        * editing/selection/mixed-editability-2-expected.txt: Added.
        * editing/selection/mixed-editability-2.html: Added.

WebCore:

        Reviewed by levi

        <rdar://problem/4609184>
        Mail crashes at WebCore::Selection::adjustForEditableContent
        <rdar://problem/4609140>
        Crash at WebCore::Range::compareBoundaryPoints

        * editing/Selection.cpp:
        (WebCore::Selection::adjustForEditableContent):
        * editing/VisiblePosition.cpp:
        (WebCore::VisiblePosition::next): Moved code into a helper function.
        (WebCore::VisiblePosition::previous): Ditto.
        * editing/htmlediting.cpp:
        (WebCore::comparePositions): Moved from Selection.cpp.
        (WebCore::lowestEditableAncestor): Added.  Returns rootEditableElement
        for a node in editable content and the rootEditableElement for the first
        ancestor that's editable for a node in non-editable content.
        (WebCore::firstEditablePositionAfterPositionInRoot):
        (WebCore::lastEditablePositionBeforePositionInRoot):
        * editing/htmlediting.h:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@15195 268f45cc-cd09-0410-ab3c-d52691b4dbfc

14 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/selection/mixed-editability-1-expected.checksum [new file with mode: 0644]
LayoutTests/editing/selection/mixed-editability-1-expected.png [new file with mode: 0644]
LayoutTests/editing/selection/mixed-editability-1-expected.txt [new file with mode: 0644]
LayoutTests/editing/selection/mixed-editability-1.html [new file with mode: 0644]
LayoutTests/editing/selection/mixed-editability-2-expected.checksum [new file with mode: 0644]
LayoutTests/editing/selection/mixed-editability-2-expected.png [new file with mode: 0644]
LayoutTests/editing/selection/mixed-editability-2-expected.txt [new file with mode: 0644]
LayoutTests/editing/selection/mixed-editability-2.html [new file with mode: 0644]
WebCore/ChangeLog
WebCore/editing/Selection.cpp
WebCore/editing/VisiblePosition.cpp
WebCore/editing/htmlediting.cpp
WebCore/editing/htmlediting.h

index 15fdf6cb5dd150f629c7165b5bd6e28e68690815..a558e82a9efbb9b1e9f1d45d5b060eaeb38a1621 100644 (file)
@@ -1,3 +1,21 @@
+2006-07-06  Justin Garcia  <justin.garcia@apple.com>
+
+        Reviewed by levi
+        
+        <rdar://problem/4609184>
+        Mail crashes at WebCore::Selection::adjustForEditableContent
+        <rdar://problem/4609140>
+        Crash at WebCore::Range::compareBoundaryPoints
+
+        * editing/selection/mixed-editability-1-expected.checksum: Added.
+        * editing/selection/mixed-editability-1-expected.png: Added.
+        * editing/selection/mixed-editability-1-expected.txt: Added.
+        * editing/selection/mixed-editability-1.html: Added.
+        * editing/selection/mixed-editability-2-expected.checksum: Added.
+        * editing/selection/mixed-editability-2-expected.png: Added.
+        * editing/selection/mixed-editability-2-expected.txt: Added.
+        * editing/selection/mixed-editability-2.html: Added.
+
 2006-07-06  Anders Carlsson  <acarlsson@apple.com>
 
         Reviewed by Adele.
diff --git a/LayoutTests/editing/selection/mixed-editability-1-expected.checksum b/LayoutTests/editing/selection/mixed-editability-1-expected.checksum
new file mode 100644 (file)
index 0000000..3a22385
--- /dev/null
@@ -0,0 +1 @@
+3dca5749d73aa0bd06f5da211a83cd8f
\ No newline at end of file
diff --git a/LayoutTests/editing/selection/mixed-editability-1-expected.png b/LayoutTests/editing/selection/mixed-editability-1-expected.png
new file mode 100644 (file)
index 0000000..4877b3f
Binary files /dev/null and b/LayoutTests/editing/selection/mixed-editability-1-expected.png differ
diff --git a/LayoutTests/editing/selection/mixed-editability-1-expected.txt b/LayoutTests/editing/selection/mixed-editability-1-expected.txt
new file mode 100644 (file)
index 0000000..061e048
--- /dev/null
@@ -0,0 +1,21 @@
+EDITING DELEGATE: shouldBeginEditingInDOMRange:range from 0 of BODY > HTML > #document to 4 of BODY > HTML > #document
+EDITING DELEGATE: webViewDidBeginEditing:WebViewDidBeginEditingNotification
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 1 of #text > SPAN > BODY > HTML > #document to 1 of #text > SPAN > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldChangeSelectedDOMRange:range from 1 of #text > SPAN > BODY > HTML > #document to 1 of #text > SPAN > BODY > HTML > #document toDOMRange:range from 0 of #text > SPAN > BODY > HTML > #document to 2 of #text > SPAN > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+layer at (0,0) size 800x600
+  RenderView at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderInline {SPAN} at (0,0) size 20x18
+        RenderText {#text} at (0,0) size 20x18
+          text run at (0,0) width 20: "On"
+      RenderText {#text} at (20,0) size 784x36
+        text run at (20,0) width 464: "ly the first two letters of the first word in this sentence should be selected. "
+        text run at (484,0) width 300: "To run this test manually, double click between "
+        text run at (0,18) width 351: "the first two letters of the first word of the first sentence."
+      RenderText {#text} at (0,0) size 0x0
+selection start: position 0 of child 0 {#text} of child 0 {SPAN} of child 0 {BODY} of child 0 {HTML} of document
+selection end:   position 2 of child 0 {#text} of child 0 {SPAN} of child 0 {BODY} of child 0 {HTML} of document
diff --git a/LayoutTests/editing/selection/mixed-editability-1.html b/LayoutTests/editing/selection/mixed-editability-1.html
new file mode 100644 (file)
index 0000000..fe4f77e
--- /dev/null
@@ -0,0 +1,21 @@
+
+<body contenteditable="true"><span id="start" contenteditable="false">On</span>ly the first two letters of the first word in this sentence should be selected.  To run this test manually, double click between the first two letters of the first word of the first sentence.</body>
+
+<script>
+if (window.layoutTestController) {
+    var s = window.getSelection();
+    
+    var start = document.getElementById("start");
+    
+    var x, y;
+    
+    x = start.offsetParent.offsetTop + start.offsetLeft + start.offsetWidth / 2;
+    y = start.offsetParent.offsetTop + start.offsetTop + start.offsetHeight / 2;
+    
+    eventSender.mouseMoveTo(x, y);
+    eventSender.mouseDown();
+    eventSender.mouseUp();
+    eventSender.mouseDown();
+    eventSender.mouseUp();
+}
+</script>
\ No newline at end of file
diff --git a/LayoutTests/editing/selection/mixed-editability-2-expected.checksum b/LayoutTests/editing/selection/mixed-editability-2-expected.checksum
new file mode 100644 (file)
index 0000000..ee08686
--- /dev/null
@@ -0,0 +1 @@
+3c823324cbf8c851a2ede3c9d3456890
\ No newline at end of file
diff --git a/LayoutTests/editing/selection/mixed-editability-2-expected.png b/LayoutTests/editing/selection/mixed-editability-2-expected.png
new file mode 100644 (file)
index 0000000..fb3ee8b
Binary files /dev/null and b/LayoutTests/editing/selection/mixed-editability-2-expected.png differ
diff --git a/LayoutTests/editing/selection/mixed-editability-2-expected.txt b/LayoutTests/editing/selection/mixed-editability-2-expected.txt
new file mode 100644 (file)
index 0000000..ec07f21
--- /dev/null
@@ -0,0 +1,21 @@
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+layer at (0,0) size 800x600
+  RenderView at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x18
+        RenderText {#text} at (0,0) size 57x18
+          text run at (0,0) width 57: "OnlyThe"
+        RenderInline {SPAN} at (0,0) size 32x18
+          RenderText {#text} at (57,0) size 32x18
+            text run at (57,0) width 32: "Bold"
+        RenderText {#text} at (89,0) size 74x18
+          text run at (89,0) width 74: "TextShould"
+        RenderInline {SPAN} at (0,0) size 18x18
+          RenderText {#text} at (163,0) size 18x18
+            text run at (163,0) width 18: "Be"
+        RenderText {#text} at (181,0) size 53x18
+          text run at (181,0) width 53: "Selected"
+selection start: position 0 of child 0 {#text} of child 1 {SPAN} of child 1 {DIV} of child 0 {BODY} of child 0 {HTML} of document
+selection end:   position 4 of child 0 {#text} of child 1 {SPAN} of child 1 {DIV} of child 0 {BODY} of child 0 {HTML} of document
diff --git a/LayoutTests/editing/selection/mixed-editability-2.html b/LayoutTests/editing/selection/mixed-editability-2.html
new file mode 100644 (file)
index 0000000..f747bc3
--- /dev/null
@@ -0,0 +1,11 @@
+<body>
+<div contenteditable="true">
+OnlyThe<span id="base" style="font-weight:bold;" contenteditable="false">Bold</span>TextShould<span id="extent">Be</span>Selected
+</div>
+<script>
+var s = window.getSelection();
+var base = document.getElementById("base");
+var extent = document.getElementById("extent");
+s.setBaseAndExtent(base, 0, extent, 0);
+</script>
+</body>
\ No newline at end of file
index 066a4e26845e365a0436a5d555eebb60a938cf1a..a10013ab35c02dc33ec8bbf5954babe7ebcd5f89 100644 (file)
@@ -1,3 +1,26 @@
+2006-07-06  Justin Garcia  <justin.garcia@apple.com>
+
+        Reviewed by levi
+        
+        <rdar://problem/4609184>
+        Mail crashes at WebCore::Selection::adjustForEditableContent
+        <rdar://problem/4609140>
+        Crash at WebCore::Range::compareBoundaryPoints
+        
+        * editing/Selection.cpp:
+        (WebCore::Selection::adjustForEditableContent):
+        * editing/VisiblePosition.cpp:
+        (WebCore::VisiblePosition::next): Moved code into a helper function.
+        (WebCore::VisiblePosition::previous): Ditto.
+        * editing/htmlediting.cpp:
+        (WebCore::comparePositions): Moved from Selection.cpp.
+        (WebCore::lowestEditableAncestor): Added.  Returns rootEditableElement
+        for a node in editable content and the rootEditableElement for the first
+        ancestor that's editable for a node in non-editable content.
+        (WebCore::firstEditablePositionAfterPositionInRoot): 
+        (WebCore::lastEditablePositionBeforePositionInRoot):
+        * editing/htmlediting.h:
+
 2006-07-06  Anders Carlsson  <acarlsson@apple.com>
 
         Reviewed by Adele.
index c673af1269c3909aa9122c8f2f2340051edc0733..16f8ed8b33a450036d803b888fefbe5bfab09a69 100644 (file)
@@ -148,38 +148,6 @@ bool Selection::expandUsingGranularity(TextGranularity granularity)
     return true;
 }
 
-// Compare two positions, taking into account the possibility that one or both
-// could be inside a shadow tree. Only works for non-null values.
-static int comparePositions(const Position& a, const Position& b)
-{
-    Node* nodeA = a.node();
-    ASSERT(nodeA);
-    Node* nodeB = b.node();
-    ASSERT(nodeB);
-    int offsetA = a.offset();
-    int offsetB = b.offset();
-
-    Node* shadowAncestorA = nodeA->shadowAncestorNode();
-    if (shadowAncestorA == nodeA)
-        shadowAncestorA = 0;
-    Node* shadowAncestorB = nodeB->shadowAncestorNode();
-    if (shadowAncestorB == nodeB)
-        shadowAncestorB = 0;
-
-    if (shadowAncestorA != shadowAncestorB) {
-        if (shadowAncestorA) {
-            nodeA = shadowAncestorA;
-            offsetA = 0;
-        }
-        if (shadowAncestorB) {
-            nodeB = shadowAncestorB;
-            offsetB = 0;
-        }
-    }
-
-    return Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB);
-}
-
 void Selection::validate()
 {
     // Move the selection to rendered positions, if possible.
@@ -329,52 +297,78 @@ void Selection::adjustForEditableContent()
     Node *startRoot = m_start.node()->rootEditableElement();
     Node *endRoot = m_end.node()->rootEditableElement();
     
+    Node* baseEditableAncestor = lowestEditableAncestor(m_base.node());
+    
     // The base, start and end are all in the same region.  No adjustment necessary.
     if (baseRoot == startRoot && baseRoot == endRoot)
         return;
     
-    // The selection is based in an editable area.  Keep both sides from reaching outside that area.
+    // The selection is based in editable content.
     if (baseRoot) {
-        // If the start is outside the base's editable root, cap it at the start of that editable root.
-        if (baseRoot != startRoot) {
-            VisiblePosition first(Position(baseRoot, 0));
+        // If the start is outside the base's editable root, cap it at the start of that root.
+        // If the start is in non-editable content that is inside the base's editable root, put it
+        // at the first editable position after start inside the base's editable root.
+        if (startRoot != baseRoot) {
+            VisiblePosition first = firstEditablePositionAfterPositionInRoot(m_start, baseRoot);
             m_start = first.deepEquivalent();
         }
-        // If the end is outside the base's editable root, cap it at the end of that editable root.
-        if (baseRoot != endRoot) {
-            VisiblePosition last(Position(baseRoot, maxDeepOffset(baseRoot)));
+        // If the end is outside the base's editable root, cap it at the end of that root.
+        // If the end is in non-editable content that is inside the base's root, put it
+        // at the last editable position before the end inside the base's root.
+        if (endRoot != baseRoot) {
+            VisiblePosition last = lastEditablePositionBeforePositionInRoot(m_end, baseRoot);
             m_end = last.deepEquivalent();
         }
-    // The selection is based outside editable content.  Keep both sides from reaching into editable content.
+    // The selection is based in non-editable content.
     } else {
-        // The selection ends in editable content, move backward until non-editable content is reached.
-        if (endRoot) {
-            VisiblePosition previous;
-            do {
-                endRoot = endRoot->shadowAncestorNode();
-                previous = VisiblePosition(Position(endRoot, 0)).previous();
-                endRoot = previous.deepEquivalent().node()->rootEditableElement();
-            } while (endRoot);
+        // FIXME: Non-editable pieces inside editable content should be atomic, in the same way that editable
+        // pieces in non-editable content are atomic.
+    
+        // The selection ends in editable content or non-editable content inside a different editable ancestor, 
+        // move backward until non-editable content inside the same lowest editable ancestor is reached.
+        Node* endEditableAncestor = lowestEditableAncestor(m_end.node());
+        if (endRoot || endEditableAncestor != baseEditableAncestor) {
+            Node* node = m_end.node();
+            VisiblePosition previous(Position(node, maxDeepOffset(node)));
+            while (node && !(lowestEditableAncestor(node) == baseEditableAncestor && !node->isContentEditable() && previous.isNotNull())) {
+                node = node->traversePreviousNode();
+                previous = VisiblePosition(Position(node, maxDeepOffset(node)));
+            }
             
-            ASSERT(!previous.isNull());
+            if (previous.isNull()) {
+                ASSERT_NOT_REACHED();
+                m_base = Position();
+                m_extent = Position();
+                validate();
+                return;
+            }
             m_end = previous.deepEquivalent();
         }
-        // The selection starts in editable content, move forward until non-editable content is reached.
-        if (startRoot) {
-            VisiblePosition next;
-            do {
-                startRoot = startRoot->shadowAncestorNode();
-                next = VisiblePosition(Position(startRoot, maxDeepOffset(startRoot))).next();
-                startRoot = next.deepEquivalent().node()->rootEditableElement();
-            } while (startRoot);
+
+        // The selection ends in editable content or non-editable content inside a different editable ancestor, 
+        // move backward until non-editable content inside the same lowest editable ancestor is reached.
+        Node* startEditableAncestor = lowestEditableAncestor(m_start.node());      
+        if (startRoot || startEditableAncestor != baseEditableAncestor) {
+            Node* node = m_start.node();
+            VisiblePosition next(Position(node, 0));
+            while (node && !(lowestEditableAncestor(node) == baseEditableAncestor && !node->isContentEditable() && next.isNotNull())) {
+                node = node->traverseNextNode();
+                next = VisiblePosition(Position(node, 0));
+            }
             
-            ASSERT(!next.isNull());
+            if (next.isNull()) {
+                ASSERT_NOT_REACHED();
+                m_base = Position();
+                m_extent = Position();
+                validate();
+                return;
+            }
             m_start = next.deepEquivalent();
         }
     }
     
     // Correct the extent if necessary.
-    if (baseRoot != m_extent.node()->rootEditableElement())
+    if (baseEditableAncestor != lowestEditableAncestor(m_extent.node()))
         m_extent = m_baseIsFirst ? m_end : m_start;
 }
 
index 4f2cb586370e1746fe981f8af62640aa0f2aac70..f6d258d6dcedf3b032c406436301db5bf3c9eec9 100644 (file)
@@ -74,19 +74,8 @@ VisiblePosition VisiblePosition::next(bool stayInEditableContent) const
 
     if (highestEditableRoot(next.deepEquivalent().node()) == highestRoot)
         return next;
-
-    Position p = next.deepEquivalent();
-    Node* node = p.node();
-    Node* child = node->childNode(p.offset());
-    node = child ? child : node->traverseNextSibling(highestRoot);
-
-    while (node && !node->isContentEditable())
-        node = node->traverseNextNode(highestRoot);
     
-    if (!node)
-        return VisiblePosition();
-
-    return VisiblePosition(Position(node, 0));
+    return firstEditablePositionAfterPositionInRoot(next.deepEquivalent(), highestRoot);
 }
 
 VisiblePosition VisiblePosition::previous(bool stayInEditableContent) const
@@ -122,18 +111,7 @@ VisiblePosition VisiblePosition::previous(bool stayInEditableContent) const
     if (highestEditableRoot(prev.deepEquivalent().node()) == highestRoot)
         return prev;
 
-    Position p = prev.deepEquivalent();
-    Node* node = p.node();
-    Node* child = node->firstChild() && p.offset() > 1 ? node->childNode(p.offset() - 1) : 0;
-    node = child ? child : node->traversePreviousNode(highestRoot);
-
-    while (node && !node->isContentEditable())
-        node = node->traversePreviousNodePostOrder(highestRoot);
-    
-    if (!node)
-        return VisiblePosition();
-
-    return VisiblePosition(Position(node, maxDeepOffset(node)));
+    return lastEditablePositionBeforePositionInRoot(prev.deepEquivalent(), highestRoot);
 }
 
 Position VisiblePosition::previousVisiblePosition(const Position& pos)
index 6dc1b4d1a6a5717af797bfa260c08340f5f82d86..51a52c91a1ba7cb91965c04e641f50505678614c 100644 (file)
@@ -35,6 +35,7 @@
 #include "HTMLNames.h"
 #include "RenderObject.h"
 #include "RegularExpression.h"
+#include "Range.h"
 
 using namespace std;
 
@@ -87,6 +88,38 @@ bool canHaveChildrenForEditing(const Node* node)
            !node->isTextNode();
 }
 
+// Compare two positions, taking into account the possibility that one or both
+// could be inside a shadow tree. Only works for non-null values.
+int comparePositions(const Position& a, const Position& b)
+{
+    Node* nodeA = a.node();
+    ASSERT(nodeA);
+    Node* nodeB = b.node();
+    ASSERT(nodeB);
+    int offsetA = a.offset();
+    int offsetB = b.offset();
+
+    Node* shadowAncestorA = nodeA->shadowAncestorNode();
+    if (shadowAncestorA == nodeA)
+        shadowAncestorA = 0;
+    Node* shadowAncestorB = nodeB->shadowAncestorNode();
+    if (shadowAncestorB == nodeB)
+        shadowAncestorB = 0;
+
+    if (shadowAncestorA != shadowAncestorB) {
+        if (shadowAncestorA) {
+            nodeA = shadowAncestorA;
+            offsetA = 0;
+        }
+        if (shadowAncestorB) {
+            nodeB = shadowAncestorB;
+            offsetB = 0;
+        }
+    }
+
+    return Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB);
+}
+
 Node* highestEditableRoot(Node* node)
 {
     if (!node)
@@ -108,6 +141,59 @@ Node* highestEditableRoot(Node* node)
     return highestRoot;
 }
 
+Node* lowestEditableAncestor(Node* node)
+{
+    if (!node)
+        return 0;
+    
+    Node *lowestRoot = 0;
+    while (node) {
+        if (node->isContentEditable())
+            return node->rootEditableElement();
+        if (node->hasTagName(bodyTag))
+            break;
+        node = node->parentNode();
+    }
+    
+    return lowestRoot;
+}
+
+VisiblePosition firstEditablePositionAfterPositionInRoot(const Position& position, Node* highestRoot)
+{
+    if (comparePositions(position, Position(highestRoot, 0)) == -1)
+        return VisiblePosition(Position(highestRoot, 0));
+
+    Node* node = position.node();
+    Node* child = node->childNode(position.offset());
+    node = child ? child : node->traverseNextSibling(highestRoot);
+
+    while (node && !node->isContentEditable())
+        node = node->traverseNextNode(highestRoot);
+    
+    if (!node)
+        return VisiblePosition();
+
+    return VisiblePosition(Position(node, 0));
+}
+
+VisiblePosition lastEditablePositionBeforePositionInRoot(const Position& position, Node* highestRoot)
+{
+    if (comparePositions(position, Position(highestRoot, maxDeepOffset(highestRoot))) == 1)
+        return VisiblePosition(Position(highestRoot, maxDeepOffset(highestRoot)));
+
+    Node* node = position.node();
+    Node* child = node->firstChild() && position.offset() > 1 ? node->childNode(position.offset() - 1) : 0;
+    node = child ? child : node->traversePreviousNode(highestRoot);
+
+    while (node && !node->isContentEditable())
+        node = node->traversePreviousNodePostOrder(highestRoot);
+    
+    if (!node)
+        return VisiblePosition();
+        
+    return VisiblePosition(Position(node, maxDeepOffset(node)));
+}
+
 // antidote for maxDeepOffset()
 Position rangeCompliantEquivalent(const Position& pos)
 {
index 19fd97a14cc386e3e1166d016ad7fb51c7f2224b..e0da87f8cd2bd99d054d8fc7b82b0ba42261596a 100644 (file)
@@ -47,6 +47,10 @@ bool isAtomicNode(const Node*);
 bool editingIgnoresContent(const Node*);
 bool canHaveChildrenForEditing(const Node*);
 Node* highestEditableRoot(Node*);
+VisiblePosition firstEditablePositionAfterPositionInRoot(const Position&, Node*);
+VisiblePosition lastEditablePositionBeforePositionInRoot(const Position&, Node*);
+int comparePositions(const Position&, const Position&);
+Node* lowestEditableAncestor(Node*);
 
 void rebalanceWhitespaceInTextNode(Node*, unsigned start, unsigned length);
 const String& nonBreakingSpaceString();