⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 249709 in webkit


Ignore:
Timestamp:
Sep 10, 2019, 3:53:59 AM (7 years ago)
Author:
rniwa@webkit.org
Message:

Option + arrow moves caret past whitespace on iOS
https://bugs.webkit.org/show_bug.cgi?id=201575

Reviewed by Wenson Hsieh.

Source/WebCore:

The bug was caused by findNextWordFromIndex on iOS behaving differently from macOS and UIKit by skipping
trailing whitespace after a word when moving forward and not skipping leading whitespace when moving backward.

This patch introduces a new mode (StopAfterWord) of findNextWordFromIndex in iOS that better matches
the behavior of findNextWordFromIndex on macOS and UIKit, and use it in various modify* functions of
FrameSelection when the selection update is triggered by user.

The legacy mode (LegacyStopBeforeWord) is used in all other call sites as well as when modify* functions
are invoked from author scripts.

Test: editing/selection/ios/move-by-word-with-keyboard.html

  • editing/FrameSelection.cpp:

(WebCore::nextWordWhitespaceModeInIOS): Added. A helper to convert EUserTriggered to NextWordModeInIOS.
(WebCore::FrameSelection::nextWordPositionForPlatform):
(WebCore::FrameSelection::modifyExtendingRight):
(WebCore::FrameSelection::modifyExtendingForward):
(WebCore::FrameSelection::modifyMovingRight):
(WebCore::FrameSelection::modifyMovingForward):
(WebCore::FrameSelection::modifyExtendingLeft):
(WebCore::FrameSelection::modifyExtendingBackward):
(WebCore::FrameSelection::modifyMovingLeft):
(WebCore::FrameSelection::modifyMovingBackward):
(WebCore::FrameSelection::modify):
(WebCore::FrameSelection::updateAppearance):

  • editing/FrameSelection.h:
  • editing/TextIterator.cpp:

(WebCore::SearchBuffer::isWordStartMatch const):

  • editing/VisibleUnits.cpp:

(WebCore::previousWordPositionBoundary):
(WebCore::previousWordPosition):
(WebCore::nextWordPositionBoundary):
(WebCore::nextWordPosition):

  • editing/VisibleUnits.h:
  • platform/text/TextBoundaries.cpp:

(WebCore::findNextWordFromIndex):

  • platform/text/TextBoundaries.h:
  • platform/text/mac/TextBoundaries.mm:

(WebCore::findNextWordFromIndex): Added a new mode.

LayoutTests:

Added a new test for moving caret by word granularity on iOS.

  • editing/selection/ios/move-by-word-with-keyboard-expected.txt: Added.
  • editing/selection/ios/move-by-word-with-keyboard.html: Added.
  • editing/selection/ios/select-non-editable-text-using-keyboard-expected.txt: Rebaselined.
  • editing/selection/ios/select-non-editable-text-using-keyboard.html: Updated the expected

selection string due to the behavior change. Also fixed a bug that some test cases were
not waiting for a secondary selectionchange event that happens after an extra selection
update with character granularity introduced in r247524.

Location:
trunk
Files:
2 added
12 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r249702 r249709  
     12019-09-10  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Option + arrow moves caret past whitespace on iOS
     4        https://bugs.webkit.org/show_bug.cgi?id=201575
     5
     6        Reviewed by Wenson Hsieh.
     7
     8        Added a new test for moving caret by word granularity on iOS.
     9
     10        * editing/selection/ios/move-by-word-with-keyboard-expected.txt: Added.
     11        * editing/selection/ios/move-by-word-with-keyboard.html: Added.
     12        * editing/selection/ios/select-non-editable-text-using-keyboard-expected.txt: Rebaselined.
     13        * editing/selection/ios/select-non-editable-text-using-keyboard.html: Updated the expected
     14        selection string due to the behavior change. Also fixed a bug that some test cases were
     15        not waiting for a secondary selectionchange event that happens after an extra selection
     16        update with character granularity introduced in r247524.
     17
    1182019-09-09  Chris Dumez  <cdumez@apple.com>
    219
  • trunk/LayoutTests/editing/selection/ios/select-non-editable-text-using-keyboard-expected.txt

    r246817 r249709  
    1212
    1313Press Shift + Option + right arrow to select to the end of the word:
    14 PASS window.getSelection().toString() is "Here's "
     14PASS window.getSelection().toString() is "Here's"
    1515
    1616Press Shift + Option + left arrow to select to the beginning of the word:
  • trunk/LayoutTests/editing/selection/ios/select-non-editable-text-using-keyboard.html

    r246817 r249709  
    5050    debug("<br>Press Shift + Option + right arrow to select to the end of the word:");
    5151    await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("rightArrow", ["shiftKey", "altKey"]) , document, "selectionchange");
    52     shouldBeEqualToString("window.getSelection().toString()", "Here's ");
     52    shouldBeEqualToString("window.getSelection().toString()", "Here's");
    5353}
    5454
     
    8080}
    8181
     82async function waitForSecondaryPresentationUpdateIfNeeded()
     83{
     84    // There might be a secondary selectionchange event after moving by paragraph boundary. See webkit.org/b/199851
     85    if (window.testRunner)
     86        await UIHelper.ensurePresentationUpdate();
     87}
     88
    8289async function testExtendSelectionUp()
    8390{
     
    8794    await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("upArrow", ["shiftKey"]) , document, "selectionchange");
    8895    shouldBeEqualToString("window.getSelection().toString()", "Here's to the crazy ones.\n\nT");
     96    await waitForSecondaryPresentationUpdateIfNeeded();
    8997}
    9098
     
    96104    await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("downArrow", ["shiftKey"]) , document, "selectionchange");
    97105    shouldBeEqualToString("window.getSelection().toString()", "The misfits.\n\nT");
     106    await waitForSecondaryPresentationUpdateIfNeeded();
    98107}
    99108
     
    108117    shouldBeEqualToString("window.getSelection().toString()", "Here's to the crazy ones.");
    109118
     119    await waitForSecondaryPresentationUpdateIfNeeded();
     120
    110121    toggleOnlyShowTestContainer();
    111122}
     
    119130    debug("<br>Press Shift + Option + up arrow to select to the beginning of the paragraph:");
    120131    await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("upArrow", ["shiftKey", "altKey"]) , document, "selectionchange");
     132
    121133    shouldBeEqualToString("window.getSelection().toString()", "The rebels.");
     134
     135    await waitForSecondaryPresentationUpdateIfNeeded();
    122136
    123137    toggleOnlyShowTestContainer();
     
    134148    shouldBeEqualToString("window.getSelection().toString()", "Here's to the crazy ones.\n\nThe misfits.\n\nThe rebels.");
    135149
     150    await waitForSecondaryPresentationUpdateIfNeeded();
     151
    136152    toggleOnlyShowTestContainer();
    137153}
     
    146162    await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("upArrow", ["shiftKey", "ctrlKey"]) , document, "selectionchange");
    147163    shouldBeEqualToString("window.getSelection().toString()", "Here's to the crazy ones.\n\nThe misfits.");
     164
     165    await waitForSecondaryPresentationUpdateIfNeeded();
    148166
    149167    toggleOnlyShowTestContainer();
  • trunk/Source/WebCore/ChangeLog

    r249702 r249709  
     12019-09-10  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Option + arrow moves caret past whitespace on iOS
     4        https://bugs.webkit.org/show_bug.cgi?id=201575
     5
     6        Reviewed by Wenson Hsieh.
     7
     8        The bug was caused by findNextWordFromIndex on iOS behaving differently from macOS and UIKit by skipping
     9        trailing whitespace after a word when moving forward and not skipping leading whitespace when moving backward.
     10
     11        This patch introduces a new mode (StopAfterWord) of findNextWordFromIndex in iOS that better matches
     12        the behavior of findNextWordFromIndex on macOS and UIKit, and use it in various modify* functions of
     13        FrameSelection when the selection update is triggered by user.
     14
     15        The legacy mode (LegacyStopBeforeWord) is used in all other call sites as well as when modify* functions
     16        are invoked from author scripts.
     17
     18        Test: editing/selection/ios/move-by-word-with-keyboard.html
     19
     20        * editing/FrameSelection.cpp:
     21        (WebCore::nextWordWhitespaceModeInIOS): Added. A helper to convert EUserTriggered to NextWordModeInIOS.
     22        (WebCore::FrameSelection::nextWordPositionForPlatform):
     23        (WebCore::FrameSelection::modifyExtendingRight):
     24        (WebCore::FrameSelection::modifyExtendingForward):
     25        (WebCore::FrameSelection::modifyMovingRight):
     26        (WebCore::FrameSelection::modifyMovingForward):
     27        (WebCore::FrameSelection::modifyExtendingLeft):
     28        (WebCore::FrameSelection::modifyExtendingBackward):
     29        (WebCore::FrameSelection::modifyMovingLeft):
     30        (WebCore::FrameSelection::modifyMovingBackward):
     31        (WebCore::FrameSelection::modify):
     32        (WebCore::FrameSelection::updateAppearance):
     33        * editing/FrameSelection.h:
     34        * editing/TextIterator.cpp:
     35        (WebCore::SearchBuffer::isWordStartMatch const):
     36        * editing/VisibleUnits.cpp:
     37        (WebCore::previousWordPositionBoundary):
     38        (WebCore::previousWordPosition):
     39        (WebCore::nextWordPositionBoundary):
     40        (WebCore::nextWordPosition):
     41        * editing/VisibleUnits.h:
     42        * platform/text/TextBoundaries.cpp:
     43        (WebCore::findNextWordFromIndex):
     44        * platform/text/TextBoundaries.h:
     45        * platform/text/mac/TextBoundaries.mm:
     46        (WebCore::findNextWordFromIndex): Added a new mode.
     47
    1482019-09-09  Chris Dumez  <cdumez@apple.com>
    249
  • trunk/Source/WebCore/editing/FrameSelection.cpp

    r247398 r249709  
    712712}
    713713
    714 VisiblePosition FrameSelection::nextWordPositionForPlatform(const VisiblePosition &originalPosition)
    715 {
    716     VisiblePosition positionAfterCurrentWord = nextWordPosition(originalPosition);
     714static NextWordModeInIOS nextWordWhitespaceModeInIOS(EUserTriggered userTriggered)
     715{
     716    return userTriggered == UserTriggered ? NextWordModeInIOS::StopAfterWord : NextWordModeInIOS::LegacyStopBeforeWord;
     717}
     718
     719VisiblePosition FrameSelection::nextWordPositionForPlatform(const VisiblePosition &originalPosition, EUserTriggered userTriggered)
     720{
     721    VisiblePosition positionAfterCurrentWord = nextWordPosition(originalPosition, nextWordWhitespaceModeInIOS(userTriggered));
    717722
    718723    if (m_frame && m_frame->editor().behavior().shouldSkipSpaceWhenMovingRight()) {
     
    721726        // semantics of previousWordPosition() this will put us at the
    722727        // beginning of the word following.
    723         VisiblePosition positionAfterSpacingAndFollowingWord = nextWordPosition(positionAfterCurrentWord);
     728        auto whitespaceMode = nextWordWhitespaceModeInIOS(userTriggered);
     729        VisiblePosition positionAfterSpacingAndFollowingWord = nextWordPosition(positionAfterCurrentWord, whitespaceMode);
    724730        if (positionAfterSpacingAndFollowingWord != positionAfterCurrentWord)
    725             positionAfterCurrentWord = previousWordPosition(positionAfterSpacingAndFollowingWord);
    726 
    727         bool movingBackwardsMovedPositionToStartOfCurrentWord = positionAfterCurrentWord == previousWordPosition(nextWordPosition(originalPosition));
     731            positionAfterCurrentWord = previousWordPosition(positionAfterSpacingAndFollowingWord, whitespaceMode);
     732
     733        bool movingBackwardsMovedPositionToStartOfCurrentWord = positionAfterCurrentWord == previousWordPosition(nextWordPosition(originalPosition, whitespaceMode), whitespaceMode);
    728734        if (movingBackwardsMovedPositionToStartOfCurrentWord)
    729735            positionAfterCurrentWord = positionAfterSpacingAndFollowingWord;
     
    740746#endif
    741747
    742 VisiblePosition FrameSelection::modifyExtendingRight(TextGranularity granularity)
     748VisiblePosition FrameSelection::modifyExtendingRight(TextGranularity granularity, EUserTriggered userTriggered)
    743749{
    744750    VisiblePosition pos(m_selection.extent(), m_selection.affinity());
     
    758764    case WordGranularity:
    759765        if (directionOfEnclosingBlock() == TextDirection::LTR)
    760             pos = nextWordPositionForPlatform(pos);
     766            pos = nextWordPositionForPlatform(pos, userTriggered);
    761767        else
    762             pos = previousWordPosition(pos);
     768            pos = previousWordPosition(pos, nextWordWhitespaceModeInIOS(userTriggered));
    763769        break;
    764770    case LineBoundary:
    765771        if (directionOfEnclosingBlock() == TextDirection::LTR)
    766             pos = modifyExtendingForward(granularity);
     772            pos = modifyExtendingForward(granularity, userTriggered);
    767773        else
    768             pos = modifyExtendingBackward(granularity);
     774            pos = modifyExtendingBackward(granularity, userTriggered);
    769775        break;
    770776    case SentenceGranularity:
     
    775781    case DocumentBoundary:
    776782        // FIXME: implement all of the above?
    777         pos = modifyExtendingForward(granularity);
     783        pos = modifyExtendingForward(granularity, userTriggered);
    778784        break;
    779785    case DocumentGranularity:
     
    787793}
    788794
    789 VisiblePosition FrameSelection::modifyExtendingForward(TextGranularity granularity)
     795VisiblePosition FrameSelection::modifyExtendingForward(TextGranularity granularity, EUserTriggered userTriggered)
    790796{
    791797    VisiblePosition pos(m_selection.extent(), m_selection.affinity());
     
    795801        break;
    796802    case WordGranularity:
    797         pos = nextWordPositionForPlatform(pos);
     803        pos = nextWordPositionForPlatform(pos, userTriggered);
    798804        break;
    799805    case SentenceGranularity:
     
    832838}
    833839
    834 VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity, bool* reachedBoundary)
     840VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity, EUserTriggered userTriggered, bool* reachedBoundary)
    835841{
    836842    if (reachedBoundary)
     
    862868    case DocumentBoundary:
    863869        // FIXME: Implement all of the above.
    864         pos = modifyMovingForward(granularity, reachedBoundary);
     870        pos = modifyMovingForward(granularity, userTriggered, reachedBoundary);
    865871        break;
    866872    case LineBoundary:
     
    874880}
    875881
    876 VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity, bool* reachedBoundary)
     882VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity, EUserTriggered userTriggered, bool* reachedBoundary)
    877883{
    878884    if (reachedBoundary)
     
    904910        break;
    905911    case WordGranularity:
    906         pos = nextWordPositionForPlatform(currentPosition);
     912        pos = nextWordPositionForPlatform(currentPosition, userTriggered);
    907913        break;
    908914    case SentenceGranularity:
     
    957963}
    958964
    959 VisiblePosition FrameSelection::modifyExtendingLeft(TextGranularity granularity)
     965VisiblePosition FrameSelection::modifyExtendingLeft(TextGranularity granularity, EUserTriggered userTriggered)
    960966{
    961967    VisiblePosition pos(m_selection.extent(), m_selection.affinity());
     
    975981    case WordGranularity:
    976982        if (directionOfEnclosingBlock() == TextDirection::LTR)
    977             pos = previousWordPosition(pos);
     983            pos = previousWordPosition(pos, nextWordWhitespaceModeInIOS(userTriggered));
    978984        else
    979             pos = nextWordPositionForPlatform(pos);
     985            pos = nextWordPositionForPlatform(pos, userTriggered);
    980986        break;
    981987    case LineBoundary:
    982988        if (directionOfEnclosingBlock() == TextDirection::LTR)
    983             pos = modifyExtendingBackward(granularity);
     989            pos = modifyExtendingBackward(granularity, userTriggered);
    984990        else
    985             pos = modifyExtendingForward(granularity);
     991            pos = modifyExtendingForward(granularity, userTriggered);
    986992        break;
    987993    case SentenceGranularity:
     
    991997    case ParagraphBoundary:
    992998    case DocumentBoundary:
    993         pos = modifyExtendingBackward(granularity);
     999        pos = modifyExtendingBackward(granularity, userTriggered);
    9941000        break;
    9951001    case DocumentGranularity:
     
    10031009}
    10041010       
    1005 VisiblePosition FrameSelection::modifyExtendingBackward(TextGranularity granularity)
     1011VisiblePosition FrameSelection::modifyExtendingBackward(TextGranularity granularity, EUserTriggered userTriggered)
    10061012{
    10071013    VisiblePosition pos(m_selection.extent(), m_selection.affinity());
     
    10161022        break;
    10171023    case WordGranularity:
    1018         pos = previousWordPosition(pos);
     1024        pos = previousWordPosition(pos, nextWordWhitespaceModeInIOS(userTriggered));
    10191025        break;
    10201026    case SentenceGranularity:
     
    10531059}
    10541060
    1055 VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity, bool* reachedBoundary)
     1061VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity, EUserTriggered userTriggered, bool* reachedBoundary)
    10561062{
    10571063    if (reachedBoundary)
     
    10831089    case DocumentBoundary:
    10841090        // FIXME: Implement all of the above.
    1085         pos = modifyMovingBackward(granularity, reachedBoundary);
     1091        pos = modifyMovingBackward(granularity, userTriggered, reachedBoundary);
    10861092        break;
    10871093    case LineBoundary:
     
    10951101}
    10961102
    1097 VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity, bool* reachedBoundary)
     1103VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity, EUserTriggered userTriggered, bool* reachedBoundary)
    10981104{
    10991105    if (reachedBoundary)
     
    11241130        break;
    11251131    case WordGranularity:
    1126         pos = previousWordPosition(currentPosition);
     1132        pos = previousWordPosition(currentPosition, nextWordWhitespaceModeInIOS(userTriggered));
    11271133        break;
    11281134    case SentenceGranularity:
     
    13311337    case DirectionRight:
    13321338        if (alter == AlterationMove)
    1333             position = modifyMovingRight(granularity, &reachedBoundary);
     1339            position = modifyMovingRight(granularity, userTriggered, &reachedBoundary);
    13341340        else
    1335             position = modifyExtendingRight(granularity);
     1341            position = modifyExtendingRight(granularity, userTriggered);
    13361342        break;
    13371343    case DirectionForward:
    13381344        if (alter == AlterationExtend)
    1339             position = modifyExtendingForward(granularity);
     1345            position = modifyExtendingForward(granularity, userTriggered);
    13401346        else
    1341             position = modifyMovingForward(granularity, &reachedBoundary);
     1347            position = modifyMovingForward(granularity, userTriggered, &reachedBoundary);
    13421348        break;
    13431349    case DirectionLeft:
    13441350        if (alter == AlterationMove)
    1345             position = modifyMovingLeft(granularity, &reachedBoundary);
     1351            position = modifyMovingLeft(granularity, userTriggered, &reachedBoundary);
    13461352        else
    1347             position = modifyExtendingLeft(granularity);
     1353            position = modifyExtendingLeft(granularity, userTriggered);
    13481354        break;
    13491355    case DirectionBackward:
    13501356        if (alter == AlterationExtend)
    1351             position = modifyExtendingBackward(granularity);
     1357            position = modifyExtendingBackward(granularity, userTriggered);
    13521358        else
    1353             position = modifyMovingBackward(granularity, &reachedBoundary);
     1359            position = modifyMovingBackward(granularity, userTriggered, &reachedBoundary);
    13541360        break;
    13551361    }
     
    21462152    // assume a valid selection. See <https://bugs.webkit.org/show_bug.cgi?id=69563> and <rdar://problem/10232866>.
    21472153#if ENABLE(TEXT_CARET)
    2148     VisiblePosition endVisiblePosition = paintBlockCursor ? modifyExtendingForward(CharacterGranularity) : oldSelection.visibleEnd();
     2154    VisiblePosition endVisiblePosition = paintBlockCursor ? modifyExtendingForward(CharacterGranularity, NotUserTriggered) : oldSelection.visibleEnd();
    21492155    VisibleSelection selection(oldSelection.visibleStart(), endVisiblePosition);
    21502156#else
  • trunk/Source/WebCore/editing/FrameSelection.h

    r247367 r249709  
    300300    VisiblePosition startForPlatform() const;
    301301    VisiblePosition endForPlatform() const;
    302     VisiblePosition nextWordPositionForPlatform(const VisiblePosition&);
    303 
    304     VisiblePosition modifyExtendingRight(TextGranularity);
    305     VisiblePosition modifyExtendingForward(TextGranularity);
    306     VisiblePosition modifyMovingRight(TextGranularity, bool* reachedBoundary = nullptr);
    307     VisiblePosition modifyMovingForward(TextGranularity, bool* reachedBoundary = nullptr);
    308     VisiblePosition modifyExtendingLeft(TextGranularity);
    309     VisiblePosition modifyExtendingBackward(TextGranularity);
    310     VisiblePosition modifyMovingLeft(TextGranularity, bool* reachedBoundary = nullptr);
    311     VisiblePosition modifyMovingBackward(TextGranularity, bool* reachedBoundary = nullptr);
     302    VisiblePosition nextWordPositionForPlatform(const VisiblePosition&, EUserTriggered);
     303
     304    VisiblePosition modifyExtendingRight(TextGranularity, EUserTriggered);
     305    VisiblePosition modifyExtendingForward(TextGranularity, EUserTriggered);
     306    VisiblePosition modifyMovingRight(TextGranularity, EUserTriggered, bool* reachedBoundary = nullptr);
     307    VisiblePosition modifyMovingForward(TextGranularity, EUserTriggered, bool* reachedBoundary = nullptr);
     308    VisiblePosition modifyExtendingLeft(TextGranularity, EUserTriggered);
     309    VisiblePosition modifyExtendingBackward(TextGranularity, EUserTriggered);
     310    VisiblePosition modifyMovingLeft(TextGranularity, EUserTriggered, bool* reachedBoundary = nullptr);
     311    VisiblePosition modifyMovingBackward(TextGranularity, EUserTriggered, bool* reachedBoundary = nullptr);
    312312
    313313    LayoutUnit lineDirectionPointForBlockDirectionNavigation(EPositionType);
  • trunk/Source/WebCore/editing/TextIterator.cpp

    r249074 r249709  
    23362336    size_t wordBreakSearchStart = start + length;
    23372337    while (wordBreakSearchStart > start)
    2338         wordBreakSearchStart = findNextWordFromIndex(StringView(m_buffer.data(), m_buffer.size()), wordBreakSearchStart, false /* backwards */);
     2338        wordBreakSearchStart = findNextWordFromIndex(StringView(m_buffer.data(), m_buffer.size()), wordBreakSearchStart, NextWordDirection::Backward);
    23392339    return wordBreakSearchStart == start;
    23402340}
  • trunk/Source/WebCore/editing/VisibleUnits.cpp

    r249701 r249709  
    762762}
    763763
     764template <NextWordModeInIOS nextWordModeInIOS>
    764765static unsigned previousWordPositionBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
    765766{
     
    769770    }
    770771    needMoreContext = false;
    771     return findNextWordFromIndex(text, offset, false);
    772 }
    773 
    774 VisiblePosition previousWordPosition(const VisiblePosition& position)
    775 {
    776     return position.honorEditingBoundaryAtOrBefore(previousBoundary(position, previousWordPositionBoundary));
    777 }
    778 
     772    return findNextWordFromIndex(text, offset, NextWordDirection::Backward, nextWordModeInIOS);
     773}
     774
     775VisiblePosition previousWordPosition(const VisiblePosition& position, NextWordModeInIOS nextWordModeInIOS)
     776{
     777    if (nextWordModeInIOS == NextWordModeInIOS::LegacyStopBeforeWord) // FIXME: Remove this code path.
     778        return position.honorEditingBoundaryAtOrBefore(previousBoundary(position, previousWordPositionBoundary<NextWordModeInIOS::LegacyStopBeforeWord>));
     779    return position.honorEditingBoundaryAtOrBefore(previousBoundary(position, previousWordPositionBoundary<NextWordModeInIOS::StopAfterWord>));
     780}
     781
     782template <NextWordModeInIOS nextWordModeInIOS>
    779783static unsigned nextWordPositionBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
    780784{
     
    784788    }
    785789    needMoreContext = false;
    786     return findNextWordFromIndex(text, offset, true);
    787 }
    788 
    789 VisiblePosition nextWordPosition(const VisiblePosition& position)
    790 {
    791     return position.honorEditingBoundaryAtOrAfter(nextBoundary(position, nextWordPositionBoundary));
     790    return findNextWordFromIndex(text, offset, NextWordDirection::Forward, nextWordModeInIOS);
     791}
     792
     793VisiblePosition nextWordPosition(const VisiblePosition& position, NextWordModeInIOS nextWordModeInIOS)
     794{
     795    if (nextWordModeInIOS == NextWordModeInIOS::LegacyStopBeforeWord) // FIXME: Remove this code path.
     796        return position.honorEditingBoundaryAtOrAfter(nextBoundary(position, nextWordPositionBoundary<NextWordModeInIOS::LegacyStopBeforeWord>));
     797    return position.honorEditingBoundaryAtOrAfter(nextBoundary(position, nextWordPositionBoundary<NextWordModeInIOS::StopAfterWord>));
    792798}
    793799
  • trunk/Source/WebCore/editing/VisibleUnits.h

    r243296 r249709  
    2727
    2828#include "EditingBoundary.h"
     29#include "TextBoundaries.h"
    2930#include "VisibleSelection.h"
    3031
     
    4142WEBCORE_EXPORT VisiblePosition startOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBoundary);
    4243WEBCORE_EXPORT VisiblePosition endOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBoundary);
    43 WEBCORE_EXPORT VisiblePosition previousWordPosition(const VisiblePosition &);
    44 WEBCORE_EXPORT VisiblePosition nextWordPosition(const VisiblePosition &);
     44WEBCORE_EXPORT VisiblePosition previousWordPosition(const VisiblePosition&, NextWordModeInIOS = NextWordModeInIOS::LegacyStopBeforeWord);
     45WEBCORE_EXPORT VisiblePosition nextWordPosition(const VisiblePosition&, NextWordModeInIOS = NextWordModeInIOS::LegacyStopBeforeWord);
    4546VisiblePosition rightWordPosition(const VisiblePosition&, bool skipsSpaceWhenMovingRight);
    4647VisiblePosition leftWordPosition(const VisiblePosition&, bool skipsSpaceWhenMovingRight);
  • trunk/Source/WebCore/platform/text/TextBoundaries.cpp

    r209907 r249709  
    6262#if !PLATFORM(COCOA)
    6363
    64 int findNextWordFromIndex(StringView text, int position, bool forward)
     64int findNextWordFromIndex(StringView text, int position, NextWordDirection direction, NextWordModeInIOS)
    6565{
    6666    UBreakIterator* it = wordBreakIterator(text);
    6767
    68     if (forward) {
     68    if (direction == NextWordDirection::Forward) {
    6969        position = ubrk_following(it, position);
    7070        while (position != UBRK_DONE) {
  • trunk/Source/WebCore/platform/text/TextBoundaries.h

    r165676 r249709  
    4848    void findWordBoundary(StringView, int position, int* start, int* end);
    4949    void findEndWordBoundary(StringView, int position, int* end);
    50     int findNextWordFromIndex(StringView, int position, bool forward);
     50
     51    enum class NextWordDirection : bool { Forward, Backward };
     52    enum class NextWordModeInIOS : bool { LegacyStopBeforeWord, StopAfterWord };
     53    int findNextWordFromIndex(StringView, int position, NextWordDirection, NextWordModeInIOS = NextWordModeInIOS::LegacyStopBeforeWord);
    5154
    5255}
  • trunk/Source/WebCore/platform/text/mac/TextBoundaries.mm

    r227552 r249709  
    230230}
    231231
    232 int findNextWordFromIndex(StringView text, int position, bool forward)
     232int findNextWordFromIndex(StringView text, int position, NextWordDirection direction, NextWordModeInIOS whitespaceModeInIOS)
    233233{   
    234234#if USE(APPKIT)
     235    UNUSED_PARAM(whitespaceModeInIOS);
    235236    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:text.createNSStringWithoutCopying().get()];
    236     int result = [attributedString nextWordFromIndex:position forward:forward];
     237    int result = [attributedString nextWordFromIndex:position forward:direction == NextWordDirection::Forward];
    237238    [attributedString release];
    238239    return result;
     
    244245    UBreakIterator* boundary = wordBreakIterator(text);
    245246    if (boundary) {
    246         if (forward) {
     247        if (direction == NextWordDirection::Forward) {
     248            if (whitespaceModeInIOS == NextWordModeInIOS::StopAfterWord) {
     249                while (static_cast<unsigned>(pos) < text.length() && isWordDelimitingCharacter(text[pos])) {
     250                    pos = ubrk_following(boundary, pos);
     251                    if (pos == UBRK_DONE)
     252                        return text.length();
     253                }
     254            }
    247255            do {
    248256                pos = ubrk_following(boundary, pos);
    249257                if (pos == UBRK_DONE)
    250                     pos = text.length();
     258                    return text.length();
    251259            } while (static_cast<unsigned>(pos) < text.length() && (pos == 0 || !isSkipCharacter(text[pos - 1])) && isSkipCharacter(text[pos]));
     260
     261            // ICU would skip the trailing whitespace. Go back.
     262            if (whitespaceModeInIOS == NextWordModeInIOS::StopAfterWord && isWordDelimitingCharacter(text[pos - 1]))
     263                pos = ubrk_preceding(boundary, pos);
    252264        }
    253265        else {
     266            if (whitespaceModeInIOS == NextWordModeInIOS::StopAfterWord && pos && isWordDelimitingCharacter(text[pos - 1])) {
     267                pos = ubrk_preceding(boundary, pos);
     268                if (pos == UBRK_DONE)
     269                    return 0;
     270            }
     271
    254272            do {
    255273                pos = ubrk_preceding(boundary, pos);
Note: See TracChangeset for help on using the changeset viewer.