WebCore:
authordarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 24 Mar 2009 00:50:58 +0000 (00:50 +0000)
committerdarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 24 Mar 2009 00:50:58 +0000 (00:50 +0000)
2009-03-23  Darin Adler  <darin@apple.com>

        Reviewed by Adele Peterson.

        Bug 24726: hit testing doesn't work right when the click is on anonymous content
        https://bugs.webkit.org/show_bug.cgi?id=24726
        rdar://problem/6696992

        Test: editing/selection/hit-test-anonymous.html

        * rendering/RenderBR.cpp:
        (WebCore::RenderBR::positionForPoint): Call createVisiblePosition instead of
        creating a VisiblePosition directly. It will handle finding non-anonymous
        content nearby if node() is 0.
        * rendering/RenderBlock.cpp:
        (WebCore::positionForPointRespectingEditingBoundaries): Ditto.
        (WebCore::positionForPointWithInlineChildren): Ditto.
        (WebCore::RenderBlock::positionForPoint): Ditto.
        * rendering/RenderBox.cpp:
        (WebCore::RenderBox::positionForPoint): Ditto.
        * rendering/RenderObject.cpp:
        (WebCore::RenderObject::positionForPoint): Ditto.
        (WebCore::RenderObject::createVisiblePosition): Added.
        * rendering/RenderObject.h: Added createVisiblePosition.
        * rendering/RenderReplaced.cpp:
        (WebCore::RenderReplaced::positionForPoint): Call createVisiblePosition.
        * rendering/RenderSVGInlineText.cpp:
        (WebCore::RenderSVGInlineText::positionForPoint): Ditto.
        * rendering/RenderText.cpp:
        (WebCore::RenderText::positionForPoint): Ditto.

LayoutTests:

2009-03-23  Darin Adler  <darin@apple.com>

        Reviewed by Adele Peterson.

        Bug 24726: hit testing doesn't work right when the click is on anonymous content
        https://bugs.webkit.org/show_bug.cgi?id=24726
        rdar://problem/6696992

        * editing/selection/hit-test-anonymous-expected.txt: Added.
        * editing/selection/hit-test-anonymous.html: Added.

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

12 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/selection/hit-test-anonymous-expected.txt [new file with mode: 0644]
LayoutTests/editing/selection/hit-test-anonymous.html [new file with mode: 0644]
WebCore/ChangeLog
WebCore/rendering/RenderBR.cpp
WebCore/rendering/RenderBlock.cpp
WebCore/rendering/RenderBox.cpp
WebCore/rendering/RenderObject.cpp
WebCore/rendering/RenderObject.h
WebCore/rendering/RenderReplaced.cpp
WebCore/rendering/RenderSVGInlineText.cpp
WebCore/rendering/RenderText.cpp

index 188adff..e6966b9 100644 (file)
@@ -1,3 +1,14 @@
+2009-03-23  Darin Adler  <darin@apple.com>
+
+        Reviewed by Adele Peterson.
+
+        Bug 24726: hit testing doesn't work right when the click is on anonymous content
+        https://bugs.webkit.org/show_bug.cgi?id=24726
+        rdar://problem/6696992
+
+        * editing/selection/hit-test-anonymous-expected.txt: Added.
+        * editing/selection/hit-test-anonymous.html: Added.
+
 2009-03-23  Simon Fraser  <simon.fraser@apple.com>
 
         Reviewed by Antti Koivisto
diff --git a/LayoutTests/editing/selection/hit-test-anonymous-expected.txt b/LayoutTests/editing/selection/hit-test-anonymous-expected.txt
new file mode 100644 (file)
index 0000000..026036e
--- /dev/null
@@ -0,0 +1,5 @@
+This tests clicking in anonymous content to see if a selection is successfully created.
+
+This is the selectable text.
+
+SUCCESS
diff --git a/LayoutTests/editing/selection/hit-test-anonymous.html b/LayoutTests/editing/selection/hit-test-anonymous.html
new file mode 100644 (file)
index 0000000..cd102d7
--- /dev/null
@@ -0,0 +1,74 @@
+<head>
+<style>
+span:before {
+    content: "<before> ";
+}
+span:after {
+    content: " <after>";
+}
+</style>
+<script>
+    function nodeAsString(node)
+    {
+        if (node && node.nodeType == Node.TEXT_NODE)
+            return "text in " + nodeAsString(node.parentNode);
+        if (node && node.nodeType == Node.ELEMENT_NODE) {
+            var id;
+            if (id = node.getAttribute("id"))
+                return id;
+        }
+        return node;
+    }
+    function selectionAsString()
+    {
+        return "(" + nodeAsString(getSelection().anchorNode)
+            + ", " + getSelection().anchorOffset
+            + "), (" + nodeAsString(getSelection().focusNode)
+            + ", " + getSelection().focusOffset + ")";        
+    }
+    function checkSelection(step, expected)
+    {
+        if (selectionAsString() !== expected) {
+            document.getElementById("result").innerHTML = "FAIL: After step " + step + " selection was " + selectionAsString();
+            return true;
+        }
+        return false;
+    }
+    function runTest()
+    {
+        if (window.layoutTestController)
+            layoutTestController.dumpAsText();
+
+        var block = document.getElementById("block");
+        var x = block.offsetLeft + 5;
+        var y = block.offsetTop + 5;
+
+        if (window.eventSender) {
+            eventSender.mouseMoveTo(x, y);
+            eventSender.mouseDown();
+            eventSender.mouseUp();
+        }
+
+        if (checkSelection(1, "(text in span, 0), (text in span, 0)"))
+            return;
+
+        x = block.offsetLeft + 200;
+
+        if (window.eventSender) {
+            eventSender.mouseMoveTo(x, y);
+            eventSender.mouseDown();
+            eventSender.mouseUp();
+        }
+
+        if (checkSelection(2, "(text in span, 24), (text in span, 24)"))
+            return;
+
+        document.getElementById("result").innerHTML = "SUCCESS";
+    }
+</script>
+</head>
+<body onload="runTest()">
+<p>This tests clicking in anonymous content to see if a selection is successfully created.</p>
+<p id="block" contentEditable style="border: 1px solid blue"><span id="span">This is the selectable text.</span></div>
+<p id="result">TEST DID NOT RUN</div>
+</body>
index 6f6ae9c..35524cc 100644 (file)
@@ -1,3 +1,34 @@
+2009-03-23  Darin Adler  <darin@apple.com>
+
+        Reviewed by Adele Peterson.
+
+        Bug 24726: hit testing doesn't work right when the click is on anonymous content
+        https://bugs.webkit.org/show_bug.cgi?id=24726
+        rdar://problem/6696992
+
+        Test: editing/selection/hit-test-anonymous.html
+
+        * rendering/RenderBR.cpp:
+        (WebCore::RenderBR::positionForPoint): Call createVisiblePosition instead of
+        creating a VisiblePosition directly. It will handle finding non-anonymous
+        content nearby if node() is 0.
+        * rendering/RenderBlock.cpp:
+        (WebCore::positionForPointRespectingEditingBoundaries): Ditto.
+        (WebCore::positionForPointWithInlineChildren): Ditto.
+        (WebCore::RenderBlock::positionForPoint): Ditto.
+        * rendering/RenderBox.cpp:
+        (WebCore::RenderBox::positionForPoint): Ditto.
+        * rendering/RenderObject.cpp:
+        (WebCore::RenderObject::positionForPoint): Ditto.
+        (WebCore::RenderObject::createVisiblePosition): Added.
+        * rendering/RenderObject.h: Added createVisiblePosition.
+        * rendering/RenderReplaced.cpp:
+        (WebCore::RenderReplaced::positionForPoint): Call createVisiblePosition.
+        * rendering/RenderSVGInlineText.cpp:
+        (WebCore::RenderSVGInlineText::positionForPoint): Ditto.
+        * rendering/RenderText.cpp:
+        (WebCore::RenderText::positionForPoint): Ditto.
+
 2009-03-23  Adele Peterson  <adele@apple.com>
 
         Reviewed by Darin Adler & Dave Hyatt.
index d100b30..f407099 100644 (file)
@@ -96,7 +96,7 @@ unsigned RenderBR::caretMaxRenderedOffset() const
 
 VisiblePosition RenderBR::positionForPoint(const IntPoint&)
 {
-    return VisiblePosition(node(), 0, DOWNSTREAM);
+    return createVisiblePosition(0, DOWNSTREAM);
 }
 
 } // namespace WebCore
index 5c34f06..56faae5 100644 (file)
@@ -3441,8 +3441,8 @@ static VisiblePosition positionForPointRespectingEditingBoundaries(RenderBox* pa
     // Otherwise return before or after the child, depending on if the click was left or right of the child
     int childMidX = child->width() / 2;
     if (xInChildCoords < childMidX)
-        return VisiblePosition(ancestor->node(), childNode->nodeIndex(), DOWNSTREAM);
-    return VisiblePosition(ancestor->node(), childNode->nodeIndex() + 1, UPSTREAM);
+        return ancestor->createVisiblePosition(childNode->nodeIndex(), DOWNSTREAM);
+    return ancestor->createVisiblePosition(childNode->nodeIndex() + 1, UPSTREAM);
 }
 
 static VisiblePosition positionForPointWithInlineChildren(RenderBlock* block, const IntPoint& pointInContents)
@@ -3450,7 +3450,7 @@ static VisiblePosition positionForPointWithInlineChildren(RenderBlock* block, co
     ASSERT(block->childrenInline());
 
     if (!block->firstRootBox())
-        return VisiblePosition(block->node(), 0, DOWNSTREAM);
+        return block->createVisiblePosition(0, DOWNSTREAM);
 
     InlineBox* closestBox = 0;
     // look for the closest line box in the root box which is at the passed-in y coordinate
@@ -3483,7 +3483,7 @@ static VisiblePosition positionForPointWithInlineChildren(RenderBlock* block, co
     // Can't reach this.  We have a root line box, but it has no kids.
     // FIXME: This should ASSERT_NOT_REACHED(), but clicking on placeholder text
     // seems to hit this codepath.
-    return VisiblePosition(block->node(), 0, DOWNSTREAM);
+    return block->createVisiblePosition(0, DOWNSTREAM);
 }
 
 VisiblePosition RenderBlock::positionForPoint(const IntPoint& point)
@@ -3498,9 +3498,9 @@ VisiblePosition RenderBlock::positionForPoint(const IntPoint& point)
 
     if (isReplaced()) {
         if (point.y() < 0 || point.y() < height() && point.x() < 0)
-            return VisiblePosition(node(), caretMinOffset(), DOWNSTREAM);
+            return createVisiblePosition(caretMinOffset(), DOWNSTREAM);
         if (point.y() >= height() || point.y() >= 0 && point.x() >= width())
-            return VisiblePosition(node(), caretMaxOffset(), DOWNSTREAM);
+            return createVisiblePosition(caretMaxOffset(), DOWNSTREAM);
     } 
 
     if (childrenInline()) {
index 2ba182e..98fef1f 100644 (file)
@@ -2674,7 +2674,7 @@ VisiblePosition RenderBox::positionForPoint(const IntPoint& point)
 {
     // no children...return this render object's element, if there is one, and offset 0
     if (!firstChild())
-        return firstDeepEditingPositionForNode(node());
+        return createVisiblePosition(firstDeepEditingPositionForNode(node()));
 
     int xPos = point.x();
     int yPos = point.y();
@@ -2685,8 +2685,8 @@ VisiblePosition RenderBox::positionForPoint(const IntPoint& point)
         
         if (xPos < 0 || xPos > right || yPos < 0 || yPos > bottom) {
             if (xPos <= right / 2)
-                return firstDeepEditingPositionForNode(node());
-            return lastDeepEditingPositionForNode(node());
+                return createVisiblePosition(firstDeepEditingPositionForNode(node()));
+            return createVisiblePosition(lastDeepEditingPositionForNode(node()));
         }
     }
 
@@ -2757,7 +2757,7 @@ VisiblePosition RenderBox::positionForPoint(const IntPoint& point)
     if (closestRenderer)
         return closestRenderer->positionForCoordinates(newX - closestRenderer->x(), newY - closestRenderer->y());
     
-    return firstDeepEditingPositionForNode(node());
+    return createVisiblePosition(firstDeepEditingPositionForNode(node()));
 }
 
 bool RenderBox::shrinkToAvoidFloats() const
index 2e0edc4..0716f74 100644 (file)
@@ -1896,7 +1896,7 @@ VisiblePosition RenderObject::positionForCoordinates(int x, int y)
 
 VisiblePosition RenderObject::positionForPoint(const IntPoint&)
 {
-    return VisiblePosition(node(), caretMinOffset(), DOWNSTREAM);
+    return createVisiblePosition(caretMinOffset(), DOWNSTREAM);
 }
 
 void RenderObject::updateDragState(bool dragOn)
@@ -2299,6 +2299,52 @@ RenderBoxModelObject* RenderObject::offsetParent() const
     return curr && curr->isBoxModelObject() ? toRenderBoxModelObject(curr) : 0;
 }
 
+VisiblePosition RenderObject::createVisiblePosition(int offset, EAffinity affinity)
+{
+    // If is is a non-anonymous renderer, then it's simple.
+    if (Node* node = this->node())
+        return VisiblePosition(node, offset, affinity);
+
+    // Find a nearby non-anonymous renderer.
+    RenderObject* child = this;
+    while (RenderObject* parent = child->parent()) {
+        // Find non-anonymous content after.
+        RenderObject* renderer = child;
+        while ((renderer = renderer->nextInPreOrder(parent))) {
+            if (Node* node = renderer->node())
+                return VisiblePosition(node, 0, DOWNSTREAM);
+        }
+
+        // Find non-anonymous content before.
+        renderer = child;
+        while ((renderer = renderer->previousInPreOrder())) {
+            if (renderer == parent)
+                break;
+            if (Node* node = renderer->node())
+                return VisiblePosition(node, numeric_limits<int>::max(), DOWNSTREAM);
+        }
+
+        // Use the parent itself unless it too is anonymous.
+        if (Node* node = parent->node())
+            return VisiblePosition(node, 0, DOWNSTREAM);
+
+        // Repeat at the next level up.
+        child = parent;
+    }
+
+    // Everything was anonymous. Give up.
+    return VisiblePosition();
+}
+
+VisiblePosition RenderObject::createVisiblePosition(const Position& position)
+{
+    if (position.container)
+        return VisiblePosition(position);
+
+    ASSERT(!node());
+    return createVisiblePosition(0, DOWNSTREAM);
+}
+
 #if ENABLE(SVG)
 
 FloatRect RenderObject::relativeBBox(bool) const
index c52b1ae..539cd82 100644 (file)
@@ -30,6 +30,7 @@
 #include "Document.h"
 #include "RenderObjectChildList.h"
 #include "RenderStyle.h"
+#include "TextAffinity.h"
 #include "TransformationMatrix.h"
 #include <wtf/UnusedParam.h>
 
@@ -39,6 +40,7 @@ class AnimationController;
 class HitTestResult;
 class InlineBox;
 class InlineFlowBox;
+class Position;
 class RenderBoxModelObject;
 class RenderInline;
 class RenderBlock;
@@ -459,6 +461,8 @@ public:
 
     VisiblePosition positionForCoordinates(int x, int y);
     virtual VisiblePosition positionForPoint(const IntPoint&);
+    VisiblePosition createVisiblePosition(int offset, EAffinity);
+    VisiblePosition createVisiblePosition(const Position&);
 
     virtual void dirtyLinesFromChangedChild(RenderObject*);
 
index dc3cff0..ff02e1b 100644 (file)
@@ -209,7 +209,7 @@ VisiblePosition RenderReplaced::positionForPoint(const IntPoint& point)
 {
     InlineBox* box = inlineBoxWrapper();
     if (!box)
-        return VisiblePosition(node(), 0, DOWNSTREAM);
+        return createVisiblePosition(0, DOWNSTREAM);
 
     // FIXME: This code is buggy if the replaced element is relative positioned.
 
@@ -219,15 +219,15 @@ VisiblePosition RenderReplaced::positionForPoint(const IntPoint& point)
     int bottom = root->nextRootBox() ? root->nextRootBox()->topOverflow() : root->bottomOverflow();
 
     if (point.y() + y() < top)
-        return VisiblePosition(node(), caretMinOffset(), DOWNSTREAM); // coordinates are above
+        return createVisiblePosition(caretMinOffset(), DOWNSTREAM); // coordinates are above
     
     if (point.y() + y() >= bottom)
-        return VisiblePosition(node(), caretMaxOffset(), DOWNSTREAM); // coordinates are below
+        return createVisiblePosition(caretMaxOffset(), DOWNSTREAM); // coordinates are below
     
     if (node()) {
         if (point.x() <= width() / 2)
-            return VisiblePosition(node(), 0, DOWNSTREAM);
-        return VisiblePosition(node(), 1, DOWNSTREAM);
+            return createVisiblePosition(0, DOWNSTREAM);
+        return createVisiblePosition(1, DOWNSTREAM);
     }
 
     return RenderBox::positionForPoint(point);
index bac4bf4..b98eba0 100644 (file)
@@ -152,13 +152,13 @@ VisiblePosition RenderSVGInlineText::positionForPoint(const IntPoint& point)
     SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(firstTextBox());
 
     if (!textBox || textLength() == 0)
-        return VisiblePosition(node(), 0, DOWNSTREAM);
+        return createVisiblePosition(0, DOWNSTREAM);
 
     SVGRootInlineBox* rootBox = textBox->svgRootInlineBox();
     RenderBlock* object = rootBox ? rootBox->block() : 0;
 
     if (!object)
-        return VisiblePosition(node(), 0, DOWNSTREAM);
+        return createVisiblePosition(0, DOWNSTREAM);
 
     int closestOffsetInBox = 0;
 
@@ -181,7 +181,7 @@ VisiblePosition RenderSVGInlineText::positionForPoint(const IntPoint& point)
         }
     }
 
-    return VisiblePosition(node(), closestOffsetInBox, DOWNSTREAM);
+    return createVisiblePosition(closestOffsetInBox, DOWNSTREAM);
 }
 
 void RenderSVGInlineText::destroy()
index 137d698..4b6cdf4 100644 (file)
@@ -316,7 +316,7 @@ InlineTextBox* RenderText::findNextInlineTextBox(int offset, int& pos) const
 VisiblePosition RenderText::positionForPoint(const IntPoint& point)
 {
     if (!firstTextBox() || textLength() == 0)
-        return VisiblePosition(node(), 0, DOWNSTREAM);
+        return createVisiblePosition(0, DOWNSTREAM);
 
     // Get the offset for the position, since this will take rtl text into account.
     int offset;
@@ -326,13 +326,13 @@ VisiblePosition RenderText::positionForPoint(const IntPoint& point)
         // at the y coordinate of the first line or above
         // and the x coordinate is to the left of the first text box left edge
         offset = firstTextBox()->offsetForPosition(point.x());
-        return VisiblePosition(node(), offset + firstTextBox()->start(), DOWNSTREAM);
+        return createVisiblePosition(offset + firstTextBox()->start(), DOWNSTREAM);
     }
     if (lastTextBox() && point.y() >= lastTextBox()->root()->topOverflow() && point.x() >= lastTextBox()->m_x + lastTextBox()->m_width) {
         // at the y coordinate of the last line or below
         // and the x coordinate is to the right of the last text box right edge
         offset = lastTextBox()->offsetForPosition(point.x());
-        return VisiblePosition(node(), offset + lastTextBox()->start(), DOWNSTREAM);
+        return createVisiblePosition(offset + lastTextBox()->start(), DOWNSTREAM);
     }
 
     InlineTextBox* lastBoxAbove = 0;
@@ -345,29 +345,29 @@ VisiblePosition RenderText::positionForPoint(const IntPoint& point)
                 if (point.x() == box->m_x)
                     // the x coordinate is equal to the left edge of this box
                     // the affinity must be downstream so the position doesn't jump back to the previous line
-                    return VisiblePosition(node(), offset + box->start(), DOWNSTREAM);
+                    return createVisiblePosition(offset + box->start(), DOWNSTREAM);
 
                 if (point.x() < box->m_x + box->m_width)
                     // and the x coordinate is to the left of the right edge of this box
                     // check to see if position goes in this box
-                    return VisiblePosition(node(), offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM);
+                    return createVisiblePosition(offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM);
 
                 if (!box->prevOnLine() && point.x() < box->m_x)
                     // box is first on line
                     // and the x coordinate is to the left of the first text box left edge
-                    return VisiblePosition(node(), offset + box->start(), DOWNSTREAM);
+                    return createVisiblePosition(offset + box->start(), DOWNSTREAM);
 
                 if (!box->nextOnLine())
                     // box is last on line
                     // and the x coordinate is to the right of the last text box right edge
                     // generate VisiblePosition, use UPSTREAM affinity if possible
-                    return VisiblePosition(node(), offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM);
+                    return createVisiblePosition(offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM);
             }
             lastBoxAbove = box;
         }
     }
 
-    return VisiblePosition(node(), lastBoxAbove ? lastBoxAbove->start() + lastBoxAbove->len() : 0, DOWNSTREAM);
+    return createVisiblePosition(lastBoxAbove ? lastBoxAbove->start() + lastBoxAbove->len() : 0, DOWNSTREAM);
 }
 
 IntRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, int* extraWidthToEndOfLine)