Modified: trunk/Source/WebKit/chromium/src/WebFrameImpl.cpp (127775 => 127776)
--- trunk/Source/WebKit/chromium/src/WebFrameImpl.cpp 2012-09-06 19:50:18 UTC (rev 127775)
+++ trunk/Source/WebKit/chromium/src/WebFrameImpl.cpp 2012-09-06 19:52:07 UTC (rev 127776)
@@ -170,6 +170,7 @@
#include "WebSecurityOrigin.h"
#include "WebViewImpl.h"
#include "XPathResult.h"
+#include "htmlediting.h"
#include "markup.h"
#include "painting/GraphicsContextBuilder.h"
#include "platform/WebFloatPoint.h"
@@ -1465,24 +1466,69 @@
void WebFrameImpl::selectRange(const WebPoint& start, const WebPoint& end)
{
- VisiblePosition startPosition = visiblePositionForWindowPoint(start);
- VisiblePosition endPosition = visiblePositionForWindowPoint(end);
+ if (start == end && moveCaret(start))
+ return;
- // To correctly handle editable boundaries, we adjust the selection by setting its extent
- // while keeping its base fixed. For a touch-based UI, this means that moving the selection
- // handles behaves like a drag-select with the mouse, which is what we want here. If both
- // endpoints changed, we need to set the extent twice.
- // FIXME: the WebFrame::SelectRange API should explicitly state which endpoint is moving.
- VisibleSelection newSelection = frame()->selection()->selection();
- if (startPosition != newSelection.visibleStart())
- newSelection = VisibleSelection(newSelection.visibleEnd(), startPosition);
- if (endPosition != newSelection.visibleEnd())
- newSelection = VisibleSelection(newSelection.visibleStart(), endPosition);
+ if (moveSelectionStart(start, true) && moveSelectionEnd(end, true))
+ return;
+ // Failed to move endpoints, probably because there's no current selection.
+ // Just set the selection explicitly (but this won't handle editable boundaries correctly).
+ VisibleSelection newSelection(visiblePositionForWindowPoint(start), visiblePositionForWindowPoint(end));
if (frame()->selection()->shouldChangeSelection(newSelection))
frame()->selection()->setSelection(newSelection, CharacterGranularity);
}
+bool WebFrameImpl::moveSelectionStart(const WebPoint& point, bool allowCollapsedSelection)
+{
+ const VisibleSelection& selection = frame()->selection()->selection();
+ if (selection.isNone())
+ return false;
+
+ VisiblePosition start = visiblePositionForWindowPoint(point);
+ if (!allowCollapsedSelection) {
+ VisiblePosition maxStart = selection.visibleEnd().previous();
+ if (comparePositions(start, maxStart) > 0)
+ start = maxStart;
+ }
+
+ // start is moving, so base=end, extent=start
+ VisibleSelection newSelection = VisibleSelection(selection.visibleEnd(), start);
+ frame()->selection()->setNonDirectionalSelectionIfNeeded(newSelection, CharacterGranularity);
+ return true;
+}
+
+bool WebFrameImpl::moveSelectionEnd(const WebPoint& point, bool allowCollapsedSelection)
+{
+ const VisibleSelection& selection = frame()->selection()->selection();
+ if (selection.isNone())
+ return false;
+
+ VisiblePosition end = visiblePositionForWindowPoint(point);
+ if (!allowCollapsedSelection) {
+ VisiblePosition minEnd = selection.visibleStart().next();
+ if (comparePositions(end, minEnd) < 0)
+ end = minEnd;
+ }
+
+ // end is moving, so base=start, extent=end
+ VisibleSelection newSelection = VisibleSelection(selection.visibleStart(), end);
+ frame()->selection()->setNonDirectionalSelectionIfNeeded(newSelection, CharacterGranularity);
+ return true;
+}
+
+bool WebFrameImpl::moveCaret(const WebPoint& point)
+{
+ FrameSelection* frameSelection = frame()->selection();
+ if (frameSelection->isNone() || !frameSelection->isContentEditable())
+ return false;
+
+ VisiblePosition pos = visiblePositionForWindowPoint(point);
+ frameSelection->setExtent(pos, UserTriggered);
+ frameSelection->setBase(frameSelection->extent(), UserTriggered);
+ return true;
+}
+
void WebFrameImpl::selectRange(const WebRange& webRange)
{
RefPtr<Range> range = static_cast<PassRefPtr<Range> >(webRange);
Modified: trunk/Source/WebKit/chromium/tests/WebFrameTest.cpp (127775 => 127776)
--- trunk/Source/WebKit/chromium/tests/WebFrameTest.cpp 2012-09-06 19:50:18 UTC (rev 127775)
+++ trunk/Source/WebKit/chromium/tests/WebFrameTest.cpp 2012-09-06 19:52:07 UTC (rev 127776)
@@ -990,10 +990,11 @@
webView->close();
}
-static WebView* selectRangeTestCreateWebView(const std::string& url)
+static WebView* createWebViewForTextSelection(const std::string& url)
{
WebView* webView = FrameTestHelpers::createWebViewAndLoad(url, true);
webView->settings()->setDefaultFontSize(12);
+ webView->enableFixedLayoutMode(false);
webView->resize(WebSize(640, 480));
return webView;
}
@@ -1012,6 +1013,11 @@
return WebPoint(rect.x + rect.width - 1, rect.y + rect.height - 1);
}
+static WebRect elementBounds(WebFrame* frame, const WebString& id)
+{
+ return frame->document().getElementById(id).boundsInViewportSpace();
+}
+
static std::string selectionAsString(WebFrame* frame)
{
return std::string(frame->selectionAsText().utf8().data());
@@ -1029,7 +1035,7 @@
registerMockedHttpURLLoad("select_range_iframe.html");
registerMockedHttpURLLoad("select_range_editable.html");
- webView = selectRangeTestCreateWebView(m_baseURL + "select_range_basic.html");
+ webView = createWebViewForTextSelection(m_baseURL + "select_range_basic.html");
frame = webView->mainFrame();
EXPECT_EQ("Some test text for testing.", selectionAsString(frame));
webView->selectionBounds(startWebRect, endWebRect);
@@ -1039,7 +1045,7 @@
EXPECT_EQ("Some test text for testing.", selectionAsString(frame));
webView->close();
- webView = selectRangeTestCreateWebView(m_baseURL + "select_range_scroll.html");
+ webView = createWebViewForTextSelection(m_baseURL + "select_range_scroll.html");
frame = webView->mainFrame();
EXPECT_EQ("Some offscreen test text for testing.", selectionAsString(frame));
webView->selectionBounds(startWebRect, endWebRect);
@@ -1049,7 +1055,7 @@
EXPECT_EQ("Some offscreen test text for testing.", selectionAsString(frame));
webView->close();
- webView = selectRangeTestCreateWebView(m_baseURL + "select_range_iframe.html");
+ webView = createWebViewForTextSelection(m_baseURL + "select_range_iframe.html");
frame = webView->mainFrame();
WebFrame* subframe = frame->findChildByExpression(WebString::fromUTF8("/html/body/iframe"));
EXPECT_EQ("Some test text for testing.", selectionAsString(subframe));
@@ -1062,7 +1068,7 @@
// Select the middle of an editable element, then try to extend the selection to the top of the document.
// The selection range should be clipped to the bounds of the editable element.
- webView = selectRangeTestCreateWebView(m_baseURL + "select_range_editable.html");
+ webView = createWebViewForTextSelection(m_baseURL + "select_range_editable.html");
frame = webView->mainFrame();
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
webView->selectionBounds(startWebRect, endWebRect);
@@ -1071,7 +1077,7 @@
webView->close();
// As above, but extending the selection to the bottom of the document.
- webView = selectRangeTestCreateWebView(m_baseURL + "select_range_editable.html");
+ webView = createWebViewForTextSelection(m_baseURL + "select_range_editable.html");
frame = webView->mainFrame();
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
webView->selectionBounds(startWebRect, endWebRect);
@@ -1080,6 +1086,176 @@
webView->close();
}
+TEST_F(WebFrameTest, MoveSelectionStart)
+{
+ registerMockedHttpURLLoad("text_selection.html");
+ WebView* webView = createWebViewForTextSelection(m_baseURL + "text_selection.html");
+ WebFrame* frame = webView->mainFrame();
+
+ // moveSelectionStart() always returns false if there's no selection.
+ EXPECT_FALSE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_1")), false));
+ EXPECT_FALSE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_1")), true));
+
+ frame->executeScript(WebScriptSource("selectElement('header_1');"));
+ EXPECT_EQ("Header 1.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "header_2")), false));
+ EXPECT_EQ("Header 1. Header 2.", selectionAsString(frame));
+
+ // Select second span. We can move the start to include the first span.
+ frame->executeScript(WebScriptSource("selectElement('header_2');"));
+ EXPECT_EQ("Header 2.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_1")), false));
+ EXPECT_EQ("Header 1. Header 2.", selectionAsString(frame));
+
+ // If allowCollapsedSelection=false we can't move the selection start beyond the current end.
+ // We end up with a single character selected.
+ frame->executeScript(WebScriptSource("selectElement('header_1');"));
+ EXPECT_EQ("Header 1.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionStart(bottomRightMinusOne(elementBounds(frame, "header_1")), false));
+ EXPECT_EQ(".", selectionAsString(frame));
+
+ // If allowCollapsedSelection=true we can move the start and end together.
+ frame->executeScript(WebScriptSource("selectElement('header_1');"));
+ EXPECT_EQ("Header 1.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionStart(bottomRightMinusOne(elementBounds(frame, "header_1")), true));
+ EXPECT_EQ("", selectionAsString(frame));
+ // Selection is a caret, not empty.
+ EXPECT_FALSE(frame->selectionRange().isNull());
+ EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_1")), true));
+ EXPECT_EQ("Header 1.", selectionAsString(frame));
+
+ // If allowCollapsedSelection=true we can move the start across the end.
+ frame->executeScript(WebScriptSource("selectElement('header_1');"));
+ EXPECT_EQ("Header 1.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionStart(bottomRightMinusOne(elementBounds(frame, "header_2")), true));
+ EXPECT_EQ(" Header 2.", selectionAsString(frame));
+
+ // Can't extend the selection part-way into an editable element.
+ frame->executeScript(WebScriptSource("selectElement('footer_2');"));
+ EXPECT_EQ("Footer 2.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "editable_2")), true));
+ EXPECT_EQ(" [ Footer 1. Footer 2.", selectionAsString(frame));
+
+ // Can extend the selection completely across editable elements.
+ frame->executeScript(WebScriptSource("selectElement('footer_2');"));
+ EXPECT_EQ("Footer 2.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_2")), true));
+ EXPECT_EQ("Header 2. ] [ Editable 1. Editable 2. ] [ Footer 1. Footer 2.", selectionAsString(frame));
+
+ // If the selection is editable text, we can't extend it into non-editable text.
+ frame->executeScript(WebScriptSource("selectElement('editable_2');"));
+ EXPECT_EQ("Editable 2.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_2")), true));
+ EXPECT_EQ("[ Editable 1. Editable 2.", selectionAsString(frame));
+
+ webView->close();
+}
+
+TEST_F(WebFrameTest, MoveSelectionEnd)
+{
+ registerMockedHttpURLLoad("text_selection.html");
+ WebView* webView = createWebViewForTextSelection(m_baseURL + "text_selection.html");
+ WebFrame* frame = webView->mainFrame();
+
+ // moveSelectionEnd() always returns false if there's no selection.
+ EXPECT_FALSE(frame->moveSelectionEnd(topLeft(elementBounds(frame, "header_1")), false));
+ EXPECT_FALSE(frame->moveSelectionEnd(topLeft(elementBounds(frame, "header_1")), true));
+
+ // Select first span. We can move the end to include the second span.
+ frame->executeScript(WebScriptSource("selectElement('header_1');"));
+ EXPECT_EQ("Header 1.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "header_2")), false));
+ EXPECT_EQ("Header 1. Header 2.", selectionAsString(frame));
+
+ // If allowCollapsedSelection=false we can't move the selection end beyond the current start.
+ // We end up with a single character selected.
+ frame->executeScript(WebScriptSource("selectElement('header_2');"));
+ EXPECT_EQ("Header 2.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionEnd(topLeft(elementBounds(frame, "header_2")), false));
+ EXPECT_EQ("H", selectionAsString(frame));
+
+ // If allowCollapsedSelection=true we can move the start and end together.
+ frame->executeScript(WebScriptSource("selectElement('header_2');"));
+ EXPECT_EQ("Header 2.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionEnd(topLeft(elementBounds(frame, "header_2")), true));
+ EXPECT_EQ("", selectionAsString(frame));
+ // Selection is a caret, not empty.
+ EXPECT_FALSE(frame->selectionRange().isNull());
+ EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "header_2")), true));
+ EXPECT_EQ("Header 2.", selectionAsString(frame));
+
+ // If allowCollapsedSelection=true we can move the end across the start.
+ frame->executeScript(WebScriptSource("selectElement('header_2');"));
+ EXPECT_EQ("Header 2.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionEnd(topLeft(elementBounds(frame, "header_1")), true));
+ EXPECT_EQ("Header 1. ", selectionAsString(frame));
+
+ // Can't extend the selection part-way into an editable element.
+ frame->executeScript(WebScriptSource("selectElement('header_1');"));
+ EXPECT_EQ("Header 1.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "editable_1")), true));
+ EXPECT_EQ("Header 1. Header 2. ] ", selectionAsString(frame));
+
+ // Can extend the selection completely across editable elements.
+ frame->executeScript(WebScriptSource("selectElement('header_1');"));
+ EXPECT_EQ("Header 1.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "footer_1")), true));
+ EXPECT_EQ("Header 1. Header 2. ] [ Editable 1. Editable 2. ] [ Footer 1.", selectionAsString(frame));
+
+ // If the selection is editable text, we can't extend it into non-editable text.
+ frame->executeScript(WebScriptSource("selectElement('editable_1');"));
+ EXPECT_EQ("Editable 1.", selectionAsString(frame));
+ EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "footer_1")), true));
+ EXPECT_EQ("Editable 1. Editable 2. ]", selectionAsString(frame));
+
+ webView->close();
+}
+
+TEST_F(WebFrameTest, MoveCaret)
+{
+ registerMockedHttpURLLoad("text_selection.html");
+ WebView* webView = createWebViewForTextSelection(m_baseURL + "text_selection.html");
+ WebFrame* frame = webView->mainFrame();
+
+ // moveCaret() returns false if there's no selection, or if it isn't editable.
+ EXPECT_FALSE(frame->moveCaret(topLeft(elementBounds(frame, "editable"))));
+ frame->executeScript(WebScriptSource("selectElement('header_1');"));
+ EXPECT_EQ("Header 1.", selectionAsString(frame));
+ EXPECT_FALSE(frame->moveCaret(topLeft(elementBounds(frame, "editable"))));
+
+ // Select the editable text span. Now moveCaret() works.
+ frame->executeScript(WebScriptSource("selectElement('editable_1');"));
+ EXPECT_EQ("Editable 1.", selectionAsString(frame));
+
+ EXPECT_TRUE(frame->moveCaret(topLeft(elementBounds(frame, "editable_1"))));
+ EXPECT_EQ("", selectionAsString(frame));
+ EXPECT_FALSE(frame->selectionRange().isNull());
+ EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "editable_1")), false));
+ EXPECT_EQ("Editable 1.", selectionAsString(frame));
+
+ EXPECT_TRUE(frame->moveCaret(bottomRightMinusOne(elementBounds(frame, "editable_2"))));
+ EXPECT_EQ("", selectionAsString(frame));
+ EXPECT_FALSE(frame->selectionRange().isNull());
+ EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "editable_2")), false));
+ EXPECT_EQ("Editable 2.", selectionAsString(frame));
+
+ // Caret is pinned at the start of the editable region.
+ EXPECT_TRUE(frame->moveCaret(topLeft(elementBounds(frame, "header_1"))));
+ EXPECT_EQ("", selectionAsString(frame));
+ EXPECT_FALSE(frame->selectionRange().isNull());
+ EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "editable_1")), false));
+ EXPECT_EQ("[ Editable 1.", selectionAsString(frame));
+
+ // Caret is pinned at the end of the editable region.
+ EXPECT_TRUE(frame->moveCaret(bottomRightMinusOne(elementBounds(frame, "footer_2"))));
+ EXPECT_EQ("", selectionAsString(frame));
+ EXPECT_FALSE(frame->selectionRange().isNull());
+ EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "editable_2")), false));
+ EXPECT_EQ("Editable 2. ]", selectionAsString(frame));
+
+ webView->close();
+}
+
class DisambiguationPopupTestWebViewClient : public WebViewClient {
public:
virtual bool didTapMultipleTargets(const WebGestureEvent&, const WebVector<WebRect>& targetRects) OVERRIDE