Changeset 127776 in webkit


Ignore:
Timestamp:
Sep 6, 2012 12:52:07 PM (12 years ago)
Author:
commit-queue@webkit.org
Message:

[chromium] Add WebFrame::moveSelectionStart, moveSelectionEnd, moveCaret
https://bugs.webkit.org/show_bug.cgi?id=93998

Patch by Iain Merrick <husky@chromium.org> on 2012-09-06
Reviewed by Adam Barth.

These provide the same functionality selectRange(WebPoint, WebPoint),
with finer-grained control needed on the Android platform. By passing
allowCollapsedSelection=false, we can ensure the selection stays at
least one character wide.

I have reimplemented WebFrameImpl::selectRange(WebPoint, WebPoint) by
calling the new methods. The existing test passes, and I've added new
tests for the new methods.

  • public/WebFrame.h:

(WebFrame):

  • src/WebFrameImpl.cpp:

(WebKit::WebFrameImpl::selectRange):
(WebKit):
(WebKit::WebFrameImpl::moveSelectionStart):
(WebKit::WebFrameImpl::moveSelectionEnd):
(WebKit::WebFrameImpl::moveCaret):

  • src/WebFrameImpl.h:

(WebFrameImpl):

  • tests/WebFrameTest.cpp:
  • tests/data/text_selection.html: Added.
Location:
trunk/Source/WebKit/chromium
Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit/chromium/ChangeLog

    r127767 r127776  
     12012-09-06  Iain Merrick  <husky@chromium.org>
     2
     3        [chromium] Add WebFrame::moveSelectionStart, moveSelectionEnd, moveCaret
     4        https://bugs.webkit.org/show_bug.cgi?id=93998
     5
     6        Reviewed by Adam Barth.
     7
     8        These provide the same functionality selectRange(WebPoint, WebPoint),
     9        with finer-grained control needed on the Android platform. By passing
     10        allowCollapsedSelection=false, we can ensure the selection stays at
     11        least one character wide.
     12
     13        I have reimplemented WebFrameImpl::selectRange(WebPoint, WebPoint) by
     14        calling the new methods. The existing test passes, and I've added new
     15        tests for the new methods.
     16
     17        * public/WebFrame.h:
     18        (WebFrame):
     19        * src/WebFrameImpl.cpp:
     20        (WebKit::WebFrameImpl::selectRange):
     21        (WebKit):
     22        (WebKit::WebFrameImpl::moveSelectionStart):
     23        (WebKit::WebFrameImpl::moveSelectionEnd):
     24        (WebKit::WebFrameImpl::moveCaret):
     25        * src/WebFrameImpl.h:
     26        (WebFrameImpl):
     27        * tests/WebFrameTest.cpp:
     28        * tests/data/text_selection.html: Added.
     29
    1302012-09-06  Robert Kroeger  <rjkroege@chromium.org>
    231
  • trunk/Source/WebKit/chromium/public/WebFrame.h

    r126609 r127776  
    473473    virtual bool selectWordAroundCaret() = 0;
    474474
     475    // DEPRECATED: Use moveSelectionStart / moveSelectionEnd / moveCaret
     476    // This method is intended for touch-based UIs, but it's missing some
     477    // functionality needed on Android, like preventing collapsed selections.
    475478    virtual void selectRange(const WebPoint& start, const WebPoint& end) = 0;
    476479
    477480    virtual void selectRange(const WebRange&) = 0;
    478481
     482    // The methods below are for adjusting the start and/or end of the current
     483    // selection by direct manipulation on a touch-based UI. To enter selection
     484    // mode in the first place, call selectRange() or send a fake mouse event.
     485
     486    // Moves the start of the current selection, keeping the end fixed.
     487    // Returns true on success, false if there is no selection to modify.
     488    virtual bool moveSelectionStart(const WebPoint&, bool allowCollapsedSelection) = 0;
     489
     490    // Moves the end of the current selection, keeping the start fixed.
     491    // Returns true on success, false if there is no selection to modify.
     492    virtual bool moveSelectionEnd(const WebPoint&, bool allowCollapsedSelection) = 0;
     493
     494    // Move both endpoints of the current selection to the given position.
     495    // The caret will remain pinned inside the current editable region.
     496    // Returns true on success, false if there is no selection or if we're not editing.
     497    virtual bool moveCaret(const WebPoint&) = 0;
    479498
    480499    // Printing ------------------------------------------------------------
  • trunk/Source/WebKit/chromium/src/WebFrameImpl.cpp

    r126609 r127776  
    171171#include "WebViewImpl.h"
    172172#include "XPathResult.h"
     173#include "htmlediting.h"
    173174#include "markup.h"
    174175#include "painting/GraphicsContextBuilder.h"
     
    14661467void WebFrameImpl::selectRange(const WebPoint& start, const WebPoint& end)
    14671468{
    1468     VisiblePosition startPosition = visiblePositionForWindowPoint(start);
    1469     VisiblePosition endPosition = visiblePositionForWindowPoint(end);
    1470 
    1471     // To correctly handle editable boundaries, we adjust the selection by setting its extent
    1472     // while keeping its base fixed. For a touch-based UI, this means that moving the selection
    1473     // handles behaves like a drag-select with the mouse, which is what we want here. If both
    1474     // endpoints changed, we need to set the extent twice.
    1475     // FIXME: the WebFrame::SelectRange API should explicitly state which endpoint is moving.
    1476     VisibleSelection newSelection = frame()->selection()->selection();
    1477     if (startPosition != newSelection.visibleStart())
    1478         newSelection = VisibleSelection(newSelection.visibleEnd(), startPosition);
    1479     if (endPosition != newSelection.visibleEnd())
    1480         newSelection = VisibleSelection(newSelection.visibleStart(), endPosition);
    1481 
     1469    if (start == end && moveCaret(start))
     1470        return;
     1471
     1472    if (moveSelectionStart(start, true) && moveSelectionEnd(end, true))
     1473        return;
     1474
     1475    // Failed to move endpoints, probably because there's no current selection.
     1476    // Just set the selection explicitly (but this won't handle editable boundaries correctly).
     1477    VisibleSelection newSelection(visiblePositionForWindowPoint(start), visiblePositionForWindowPoint(end));   
    14821478    if (frame()->selection()->shouldChangeSelection(newSelection))
    14831479        frame()->selection()->setSelection(newSelection, CharacterGranularity);
     1480}
     1481
     1482bool WebFrameImpl::moveSelectionStart(const WebPoint& point, bool allowCollapsedSelection)
     1483{
     1484    const VisibleSelection& selection = frame()->selection()->selection();
     1485    if (selection.isNone())
     1486        return false;
     1487
     1488    VisiblePosition start = visiblePositionForWindowPoint(point);
     1489    if (!allowCollapsedSelection) {
     1490        VisiblePosition maxStart = selection.visibleEnd().previous();
     1491        if (comparePositions(start, maxStart) > 0)
     1492            start = maxStart;
     1493    }
     1494
     1495    // start is moving, so base=end, extent=start
     1496    VisibleSelection newSelection = VisibleSelection(selection.visibleEnd(), start);
     1497    frame()->selection()->setNonDirectionalSelectionIfNeeded(newSelection, CharacterGranularity);
     1498    return true;
     1499}
     1500
     1501bool WebFrameImpl::moveSelectionEnd(const WebPoint& point, bool allowCollapsedSelection)
     1502{
     1503    const VisibleSelection& selection = frame()->selection()->selection();
     1504    if (selection.isNone())
     1505        return false;
     1506
     1507    VisiblePosition end = visiblePositionForWindowPoint(point);
     1508    if (!allowCollapsedSelection) {
     1509        VisiblePosition minEnd = selection.visibleStart().next();
     1510        if (comparePositions(end, minEnd) < 0)
     1511            end = minEnd;
     1512    }
     1513
     1514    // end is moving, so base=start, extent=end
     1515    VisibleSelection newSelection = VisibleSelection(selection.visibleStart(), end);
     1516    frame()->selection()->setNonDirectionalSelectionIfNeeded(newSelection, CharacterGranularity);
     1517    return true;
     1518}
     1519
     1520bool WebFrameImpl::moveCaret(const WebPoint& point)
     1521{
     1522    FrameSelection* frameSelection = frame()->selection();
     1523    if (frameSelection->isNone() || !frameSelection->isContentEditable())
     1524        return false;
     1525
     1526    VisiblePosition pos = visiblePositionForWindowPoint(point);
     1527    frameSelection->setExtent(pos, UserTriggered);
     1528    frameSelection->setBase(frameSelection->extent(), UserTriggered);
     1529    return true;
    14841530}
    14851531
  • trunk/Source/WebKit/chromium/src/WebFrameImpl.h

    r127757 r127776  
    187187    virtual void selectRange(const WebPoint& start, const WebPoint& end);
    188188    virtual void selectRange(const WebRange&);
     189    virtual bool moveSelectionStart(const WebPoint&, bool allowCollapsedSelection);
     190    virtual bool moveSelectionEnd(const WebPoint&, bool allowCollapsedSelection);
     191    virtual bool moveCaret(const WebPoint&);
    189192    virtual int printBegin(const WebPrintParams&,
    190193                           const WebNode& constrainToNode,
  • trunk/Source/WebKit/chromium/tests/WebFrameTest.cpp

    r127690 r127776  
    991991}
    992992
    993 static WebView* selectRangeTestCreateWebView(const std::string& url)
     993static WebView* createWebViewForTextSelection(const std::string& url)
    994994{
    995995    WebView* webView = FrameTestHelpers::createWebViewAndLoad(url, true);
    996996    webView->settings()->setDefaultFontSize(12);
     997    webView->enableFixedLayoutMode(false);
    997998    webView->resize(WebSize(640, 480));
    998999    return webView;
     
    10131014}
    10141015
     1016static WebRect elementBounds(WebFrame* frame, const WebString& id)
     1017{
     1018    return frame->document().getElementById(id).boundsInViewportSpace();
     1019}
     1020
    10151021static std::string selectionAsString(WebFrame* frame)
    10161022{
     
    10301036    registerMockedHttpURLLoad("select_range_editable.html");
    10311037
    1032     webView = selectRangeTestCreateWebView(m_baseURL + "select_range_basic.html");
     1038    webView = createWebViewForTextSelection(m_baseURL + "select_range_basic.html");
    10331039    frame = webView->mainFrame();
    10341040    EXPECT_EQ("Some test text for testing.", selectionAsString(frame));
     
    10401046    webView->close();
    10411047
    1042     webView = selectRangeTestCreateWebView(m_baseURL + "select_range_scroll.html");
     1048    webView = createWebViewForTextSelection(m_baseURL + "select_range_scroll.html");
    10431049    frame = webView->mainFrame();
    10441050    EXPECT_EQ("Some offscreen test text for testing.", selectionAsString(frame));
     
    10501056    webView->close();
    10511057
    1052     webView = selectRangeTestCreateWebView(m_baseURL + "select_range_iframe.html");
     1058    webView = createWebViewForTextSelection(m_baseURL + "select_range_iframe.html");
    10531059    frame = webView->mainFrame();
    10541060    WebFrame* subframe = frame->findChildByExpression(WebString::fromUTF8("/html/body/iframe"));
     
    10631069    // Select the middle of an editable element, then try to extend the selection to the top of the document.
    10641070    // The selection range should be clipped to the bounds of the editable element.
    1065     webView = selectRangeTestCreateWebView(m_baseURL + "select_range_editable.html");
     1071    webView = createWebViewForTextSelection(m_baseURL + "select_range_editable.html");
    10661072    frame = webView->mainFrame();
    10671073    EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
     
    10721078
    10731079    // As above, but extending the selection to the bottom of the document.
    1074     webView = selectRangeTestCreateWebView(m_baseURL + "select_range_editable.html");
     1080    webView = createWebViewForTextSelection(m_baseURL + "select_range_editable.html");
    10751081    frame = webView->mainFrame();
    10761082    EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
     
    10781084    frame->selectRange(topLeft(startWebRect), WebPoint(640, 480));
    10791085    EXPECT_EQ("This text is initially selected. 16-char footer.", selectionAsString(frame));
     1086    webView->close();
     1087}
     1088
     1089TEST_F(WebFrameTest, MoveSelectionStart)
     1090{
     1091    registerMockedHttpURLLoad("text_selection.html");
     1092    WebView* webView = createWebViewForTextSelection(m_baseURL + "text_selection.html");
     1093    WebFrame* frame = webView->mainFrame();
     1094
     1095    // moveSelectionStart() always returns false if there's no selection.
     1096    EXPECT_FALSE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_1")), false));
     1097    EXPECT_FALSE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_1")), true));
     1098
     1099    frame->executeScript(WebScriptSource("selectElement('header_1');"));
     1100    EXPECT_EQ("Header 1.", selectionAsString(frame));
     1101    EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "header_2")), false));
     1102    EXPECT_EQ("Header 1. Header 2.", selectionAsString(frame));
     1103
     1104    // Select second span. We can move the start to include the first span.
     1105    frame->executeScript(WebScriptSource("selectElement('header_2');"));
     1106    EXPECT_EQ("Header 2.", selectionAsString(frame));
     1107    EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_1")), false));
     1108    EXPECT_EQ("Header 1. Header 2.", selectionAsString(frame));
     1109
     1110    // If allowCollapsedSelection=false we can't move the selection start beyond the current end.
     1111    // We end up with a single character selected.
     1112    frame->executeScript(WebScriptSource("selectElement('header_1');"));
     1113    EXPECT_EQ("Header 1.", selectionAsString(frame));
     1114    EXPECT_TRUE(frame->moveSelectionStart(bottomRightMinusOne(elementBounds(frame, "header_1")), false));
     1115    EXPECT_EQ(".", selectionAsString(frame));
     1116
     1117    // If allowCollapsedSelection=true we can move the start and end together.
     1118    frame->executeScript(WebScriptSource("selectElement('header_1');"));
     1119    EXPECT_EQ("Header 1.", selectionAsString(frame));
     1120    EXPECT_TRUE(frame->moveSelectionStart(bottomRightMinusOne(elementBounds(frame, "header_1")), true));
     1121    EXPECT_EQ("", selectionAsString(frame));
     1122    // Selection is a caret, not empty.
     1123    EXPECT_FALSE(frame->selectionRange().isNull());
     1124    EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_1")), true));
     1125    EXPECT_EQ("Header 1.", selectionAsString(frame));
     1126
     1127    // If allowCollapsedSelection=true we can move the start across the end.
     1128    frame->executeScript(WebScriptSource("selectElement('header_1');"));
     1129    EXPECT_EQ("Header 1.", selectionAsString(frame));
     1130    EXPECT_TRUE(frame->moveSelectionStart(bottomRightMinusOne(elementBounds(frame, "header_2")), true));
     1131    EXPECT_EQ(" Header 2.", selectionAsString(frame));
     1132
     1133    // Can't extend the selection part-way into an editable element.
     1134    frame->executeScript(WebScriptSource("selectElement('footer_2');"));
     1135    EXPECT_EQ("Footer 2.", selectionAsString(frame));
     1136    EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "editable_2")), true));
     1137    EXPECT_EQ(" [ Footer 1. Footer 2.", selectionAsString(frame));
     1138
     1139    // Can extend the selection completely across editable elements.
     1140    frame->executeScript(WebScriptSource("selectElement('footer_2');"));
     1141    EXPECT_EQ("Footer 2.", selectionAsString(frame));
     1142    EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_2")), true));
     1143    EXPECT_EQ("Header 2. ] [ Editable 1. Editable 2. ] [ Footer 1. Footer 2.", selectionAsString(frame));
     1144
     1145    // If the selection is editable text, we can't extend it into non-editable text.
     1146    frame->executeScript(WebScriptSource("selectElement('editable_2');"));
     1147    EXPECT_EQ("Editable 2.", selectionAsString(frame));
     1148    EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "header_2")), true));
     1149    EXPECT_EQ("[ Editable 1. Editable 2.", selectionAsString(frame));
     1150
     1151    webView->close();
     1152}
     1153
     1154TEST_F(WebFrameTest, MoveSelectionEnd)
     1155{
     1156    registerMockedHttpURLLoad("text_selection.html");
     1157    WebView* webView = createWebViewForTextSelection(m_baseURL + "text_selection.html");
     1158    WebFrame* frame = webView->mainFrame();
     1159
     1160    // moveSelectionEnd() always returns false if there's no selection.
     1161    EXPECT_FALSE(frame->moveSelectionEnd(topLeft(elementBounds(frame, "header_1")), false));
     1162    EXPECT_FALSE(frame->moveSelectionEnd(topLeft(elementBounds(frame, "header_1")), true));
     1163
     1164    // Select first span. We can move the end to include the second span.
     1165    frame->executeScript(WebScriptSource("selectElement('header_1');"));
     1166    EXPECT_EQ("Header 1.", selectionAsString(frame));
     1167    EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "header_2")), false));
     1168    EXPECT_EQ("Header 1. Header 2.", selectionAsString(frame));
     1169
     1170    // If allowCollapsedSelection=false we can't move the selection end beyond the current start.
     1171    // We end up with a single character selected.
     1172    frame->executeScript(WebScriptSource("selectElement('header_2');"));
     1173    EXPECT_EQ("Header 2.", selectionAsString(frame));
     1174    EXPECT_TRUE(frame->moveSelectionEnd(topLeft(elementBounds(frame, "header_2")), false));
     1175    EXPECT_EQ("H", selectionAsString(frame));
     1176
     1177    // If allowCollapsedSelection=true we can move the start and end together.
     1178    frame->executeScript(WebScriptSource("selectElement('header_2');"));
     1179    EXPECT_EQ("Header 2.", selectionAsString(frame));
     1180    EXPECT_TRUE(frame->moveSelectionEnd(topLeft(elementBounds(frame, "header_2")), true));
     1181    EXPECT_EQ("", selectionAsString(frame));
     1182    // Selection is a caret, not empty.
     1183    EXPECT_FALSE(frame->selectionRange().isNull());
     1184    EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "header_2")), true));
     1185    EXPECT_EQ("Header 2.", selectionAsString(frame));
     1186
     1187    // If allowCollapsedSelection=true we can move the end across the start.
     1188    frame->executeScript(WebScriptSource("selectElement('header_2');"));
     1189    EXPECT_EQ("Header 2.", selectionAsString(frame));
     1190    EXPECT_TRUE(frame->moveSelectionEnd(topLeft(elementBounds(frame, "header_1")), true));
     1191    EXPECT_EQ("Header 1. ", selectionAsString(frame));
     1192
     1193    // Can't extend the selection part-way into an editable element.
     1194    frame->executeScript(WebScriptSource("selectElement('header_1');"));
     1195    EXPECT_EQ("Header 1.", selectionAsString(frame));
     1196    EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "editable_1")), true));
     1197    EXPECT_EQ("Header 1. Header 2. ] ", selectionAsString(frame));
     1198
     1199    // Can extend the selection completely across editable elements.
     1200    frame->executeScript(WebScriptSource("selectElement('header_1');"));
     1201    EXPECT_EQ("Header 1.", selectionAsString(frame));
     1202    EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "footer_1")), true));
     1203    EXPECT_EQ("Header 1. Header 2. ] [ Editable 1. Editable 2. ] [ Footer 1.", selectionAsString(frame));
     1204
     1205    // If the selection is editable text, we can't extend it into non-editable text.
     1206    frame->executeScript(WebScriptSource("selectElement('editable_1');"));
     1207    EXPECT_EQ("Editable 1.", selectionAsString(frame));
     1208    EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "footer_1")), true));
     1209    EXPECT_EQ("Editable 1. Editable 2. ]", selectionAsString(frame));
     1210
     1211    webView->close();
     1212}
     1213
     1214TEST_F(WebFrameTest, MoveCaret)
     1215{
     1216    registerMockedHttpURLLoad("text_selection.html");
     1217    WebView* webView = createWebViewForTextSelection(m_baseURL + "text_selection.html");
     1218    WebFrame* frame = webView->mainFrame();
     1219
     1220    // moveCaret() returns false if there's no selection, or if it isn't editable.
     1221    EXPECT_FALSE(frame->moveCaret(topLeft(elementBounds(frame, "editable"))));
     1222    frame->executeScript(WebScriptSource("selectElement('header_1');"));
     1223    EXPECT_EQ("Header 1.", selectionAsString(frame));
     1224    EXPECT_FALSE(frame->moveCaret(topLeft(elementBounds(frame, "editable"))));
     1225
     1226    // Select the editable text span. Now moveCaret() works.
     1227    frame->executeScript(WebScriptSource("selectElement('editable_1');"));
     1228    EXPECT_EQ("Editable 1.", selectionAsString(frame));
     1229
     1230    EXPECT_TRUE(frame->moveCaret(topLeft(elementBounds(frame, "editable_1"))));
     1231    EXPECT_EQ("", selectionAsString(frame));
     1232    EXPECT_FALSE(frame->selectionRange().isNull());
     1233    EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "editable_1")), false));
     1234    EXPECT_EQ("Editable 1.", selectionAsString(frame));
     1235
     1236    EXPECT_TRUE(frame->moveCaret(bottomRightMinusOne(elementBounds(frame, "editable_2"))));
     1237    EXPECT_EQ("", selectionAsString(frame));
     1238    EXPECT_FALSE(frame->selectionRange().isNull());
     1239    EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "editable_2")), false));
     1240    EXPECT_EQ("Editable 2.", selectionAsString(frame));
     1241
     1242    // Caret is pinned at the start of the editable region.
     1243    EXPECT_TRUE(frame->moveCaret(topLeft(elementBounds(frame, "header_1"))));
     1244    EXPECT_EQ("", selectionAsString(frame));
     1245    EXPECT_FALSE(frame->selectionRange().isNull());
     1246    EXPECT_TRUE(frame->moveSelectionEnd(bottomRightMinusOne(elementBounds(frame, "editable_1")), false));
     1247    EXPECT_EQ("[ Editable 1.", selectionAsString(frame));
     1248
     1249    // Caret is pinned at the end of the editable region.
     1250    EXPECT_TRUE(frame->moveCaret(bottomRightMinusOne(elementBounds(frame, "footer_2"))));
     1251    EXPECT_EQ("", selectionAsString(frame));
     1252    EXPECT_FALSE(frame->selectionRange().isNull());
     1253    EXPECT_TRUE(frame->moveSelectionStart(topLeft(elementBounds(frame, "editable_2")), false));
     1254    EXPECT_EQ("Editable 2. ]", selectionAsString(frame));
     1255
    10801256    webView->close();
    10811257}
Note: See TracChangeset for help on using the changeset viewer.