Title: [249709] trunk
Revision
249709
Author
rn...@webkit.org
Date
2019-09-10 03:53:59 -0700 (Tue, 10 Sep 2019)

Log 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.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (249708 => 249709)


--- trunk/LayoutTests/ChangeLog	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/LayoutTests/ChangeLog	2019-09-10 10:53:59 UTC (rev 249709)
@@ -1,3 +1,20 @@
+2019-09-10  Ryosuke Niwa  <rn...@webkit.org>
+
+        Option + arrow moves caret past whitespace on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=201575
+
+        Reviewed by Wenson Hsieh.
+
+        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.
+
 2019-09-09  Chris Dumez  <cdu...@apple.com>
 
         REGRESSION: http/tests/resourceLoadStatistics/do-not-capture-statistics-for-simple-top-navigations.html is frequently timing out on iOS EWS bots

Added: trunk/LayoutTests/editing/selection/ios/move-by-word-with-keyboard-expected.txt (0 => 249709)


--- trunk/LayoutTests/editing/selection/ios/move-by-word-with-keyboard-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/move-by-word-with-keyboard-expected.txt	2019-09-10 10:53:59 UTC (rev 249709)
@@ -0,0 +1,423 @@
+This tests moving the caret with word granularity on iOS.
+WebKit should stop at the end of each word before whitespace when moving forwards, and after whitespace when moving backwards,
+The empty line should be skipped in either direction.
+
+Before moving forwards:
+| "
+    "
+| <div>
+|   "<#selection-caret>hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue."
+| "
+"
+
+After moving to the right by word (1):
+| "
+    "
+| <div>
+|   "hello<#selection-caret> world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue."
+| "
+"
+
+After moving to the right by word (2):
+| "
+    "
+| <div>
+|   "hello world<#selection-caret>"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue."
+| "
+"
+
+After moving to the right by word (3):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red,<#selection-caret> green, & blue."
+| "
+"
+
+After moving to the right by word (4):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green,<#selection-caret> & blue."
+| "
+"
+
+After moving to the right by word (5):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, &<#selection-caret> blue."
+| "
+"
+
+After moving to the right by word (6):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue.<#selection-caret>"
+| "
+"
+
+Before moving backwards:
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue.<#selection-caret>"
+| "
+"
+
+After moving to the left by word (1):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & <#selection-caret>blue."
+| "
+"
+
+After moving to the left by word (2):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, <#selection-caret>& blue."
+| "
+"
+
+After moving to the left by word (3):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, <#selection-caret>green, & blue."
+| "
+"
+
+After moving to the left by word (4):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "<#selection-caret>red, green, & blue."
+| "
+"
+
+After moving to the left by word (5):
+| "
+    "
+| <div>
+|   "hello <#selection-caret>world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue."
+| "
+"
+
+After moving to the left by word (6):
+| "
+    "
+| <div>
+|   "<#selection-caret>hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue."
+| "
+"
+
+Before extending forwards:
+| "
+    "
+| <div>
+|   "<#selection-caret>hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue."
+| "
+"
+
+After extending to the right by word (1):
+| "
+    "
+| <div>
+|   "<#selection-anchor>hello<#selection-focus> world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue."
+| "
+"
+
+After extending to the right by word (2):
+| "
+    "
+| <div>
+|   "<#selection-anchor>hello world<#selection-focus>"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue."
+| "
+"
+
+After extending to the right by word (3):
+| "
+    "
+| <div>
+|   "<#selection-anchor>hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red,<#selection-focus> green, & blue."
+| "
+"
+
+After extending to the right by word (4):
+| "
+    "
+| <div>
+|   "<#selection-anchor>hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green,<#selection-focus> & blue."
+| "
+"
+
+After extending to the right by word (5):
+| "
+    "
+| <div>
+|   "<#selection-anchor>hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, &<#selection-focus> blue."
+| "
+"
+
+After extending to the right by word (6):
+| "
+    "
+| <div>
+|   "<#selection-anchor>hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue.<#selection-focus>"
+| "
+"
+
+Before extending backwards:
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue.<#selection-caret>"
+| "
+"
+
+After extending to the left by word (1):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & <#selection-focus>blue.<#selection-anchor>"
+| "
+"
+
+After extending to the left by word (2):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, <#selection-focus>& blue.<#selection-anchor>"
+| "
+"
+
+After extending to the left by word (3):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, <#selection-focus>green, & blue.<#selection-anchor>"
+| "
+"
+
+After extending to the left by word (4):
+| "
+    "
+| <div>
+|   "hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "<#selection-focus>red, green, & blue.<#selection-anchor>"
+| "
+"
+
+After extending to the left by word (5):
+| "
+    "
+| <div>
+|   "hello <#selection-focus>world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue.<#selection-anchor>"
+| "
+"
+
+After extending to the left by word (6):
+| "
+    "
+| <div>
+|   "<#selection-focus>hello world"
+| "
+    "
+| <br>
+| "
+    "
+| <div>
+|   "red, green, & blue.<#selection-anchor>"
+| "
+"

Added: trunk/LayoutTests/editing/selection/ios/move-by-word-with-keyboard.html (0 => 249709)


--- trunk/LayoutTests/editing/selection/ios/move-by-word-with-keyboard.html	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/move-by-word-with-keyboard.html	2019-09-10 10:53:59 UTC (rev 249709)
@@ -0,0 +1,79 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no">
+<script src=""
+<script src=""
+<script>
+
+Markup.description(`This tests moving the caret with word granularity on iOS.
+WebKit should stop at the end of each word before whitespace when moving forwards, and after whitespace when moving backwards,
+The empty line should be skipped in either direction.`);
+Markup.noAutoDump();
+Markup.waitUntilDone();
+
+async function runTest()
+{
+    const editor = document.getElementById('editor');
+
+    if (window.testRunner)
+        await UIHelper.activateElementAndWaitForInputSession(editor);
+
+    if (!window.testRunner) {
+        Markup.dump(editor, 'Markup');
+        Markup.notifyDone();
+        return;
+    }
+
+    getSelection().setPosition(editor, 0);
+    Markup.dump(editor, 'Before moving forwards');
+
+    const moveCount = 6;
+
+    for (let i = 0; i < moveCount; i++) {
+        await UIHelper.keyDown('rightArrow', ['altKey']);
+        await UIHelper.ensurePresentationUpdate();
+        Markup.dump(editor, `After moving to the right by word (${i + 1})`);
+    }
+
+    getSelection().setPosition(editor, editor.childNodes.length);
+    Markup.dump(editor, 'Before moving backwards');
+
+    for (let i = 0; i < moveCount; i++) {
+        await UIHelper.keyDown('leftArrow', ['altKey']);
+        await UIHelper.ensurePresentationUpdate();
+        Markup.dump(editor, `After moving to the left by word (${i + 1})`);
+    }
+
+    getSelection().setPosition(editor, 0);
+    Markup.dump(editor, 'Before extending forwards');
+
+    for (let i = 0; i < moveCount; i++) {
+        await UIHelper.keyDown('rightArrow', ['shiftKey', 'altKey']);
+        await UIHelper.ensurePresentationUpdate();
+        Markup.dump(editor, `After extending to the right by word (${i + 1})`);
+    }
+
+    getSelection().setPosition(editor, editor.childNodes.length);
+    Markup.dump(editor, 'Before extending backwards');
+
+    for (let i = 0; i < moveCount; i++) {
+        await UIHelper.keyDown('leftArrow', ['shiftKey', 'altKey']);
+        await UIHelper.ensurePresentationUpdate();
+        Markup.dump(editor, `After extending to the left by word (${i + 1})`);
+    }
+
+    Markup.notifyDone();
+}
+
+_onload_ = runTest;
+</script>
+</head>
+<body>
+<div id="editor" contenteditable>
+    <div>hello world</div>
+    <br>
+    <div>red, green, & blue.</div>
+</div>
+</body>
+</html>

Modified: trunk/LayoutTests/editing/selection/ios/select-non-editable-text-using-keyboard-expected.txt (249708 => 249709)


--- trunk/LayoutTests/editing/selection/ios/select-non-editable-text-using-keyboard-expected.txt	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/LayoutTests/editing/selection/ios/select-non-editable-text-using-keyboard-expected.txt	2019-09-10 10:53:59 UTC (rev 249709)
@@ -11,7 +11,7 @@
 PASS window.getSelection().toString() is "Her"
 
 Press Shift + Option + right arrow to select to the end of the word:
-PASS window.getSelection().toString() is "Here's "
+PASS window.getSelection().toString() is "Here's"
 
 Press Shift + Option + left arrow to select to the beginning of the word:
 PASS window.getSelection().toString() is "The"

Modified: trunk/LayoutTests/editing/selection/ios/select-non-editable-text-using-keyboard.html (249708 => 249709)


--- trunk/LayoutTests/editing/selection/ios/select-non-editable-text-using-keyboard.html	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/LayoutTests/editing/selection/ios/select-non-editable-text-using-keyboard.html	2019-09-10 10:53:59 UTC (rev 249709)
@@ -49,7 +49,7 @@
 
     debug("<br>Press Shift + Option + right arrow to select to the end of the word:");
     await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("rightArrow", ["shiftKey", "altKey"]) , document, "selectionchange");
-    shouldBeEqualToString("window.getSelection().toString()", "Here's ");
+    shouldBeEqualToString("window.getSelection().toString()", "Here's");
 }
 
 async function testExtendSelectionToBeginningOfWord()
@@ -79,6 +79,13 @@
     shouldBeEqualToString("window.getSelection().toString()", "The misfits.");
 }
 
+async function waitForSecondaryPresentationUpdateIfNeeded()
+{
+    // There might be a secondary selectionchange event after moving by paragraph boundary. See webkit.org/b/199851
+    if (window.testRunner)
+        await UIHelper.ensurePresentationUpdate();
+}
+
 async function testExtendSelectionUp()
 {
     await UIHelper.callFunctionAndWaitForEvent(() => selection.setBaseAndExtent(paragraphs[1].firstChild, 1, paragraphs[1].firstChild, 0), document, "selectionchange");
@@ -86,6 +93,7 @@
     debug("<br>Press Shift + up arrow to select up:");
     await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("upArrow", ["shiftKey"]) , document, "selectionchange");
     shouldBeEqualToString("window.getSelection().toString()", "Here's to the crazy ones.\n\nT");
+    await waitForSecondaryPresentationUpdateIfNeeded();
 }
 
 async function testExtendSelectionDown()
@@ -95,6 +103,7 @@
     debug("<br>Press Shift + down arrow to select down:");
     await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("downArrow", ["shiftKey"]) , document, "selectionchange");
     shouldBeEqualToString("window.getSelection().toString()", "The misfits.\n\nT");
+    await waitForSecondaryPresentationUpdateIfNeeded();
 }
 
 async function testExtendSelectionToEndOfParagraph()
@@ -107,6 +116,8 @@
     await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("downArrow", ["shiftKey", "altKey"]) , document, "selectionchange");
     shouldBeEqualToString("window.getSelection().toString()", "Here's to the crazy ones.");
 
+    await waitForSecondaryPresentationUpdateIfNeeded();
+
     toggleOnlyShowTestContainer();
 }
 
@@ -118,8 +129,11 @@
 
     debug("<br>Press Shift + Option + up arrow to select to the beginning of the paragraph:");
     await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("upArrow", ["shiftKey", "altKey"]) , document, "selectionchange");
+
     shouldBeEqualToString("window.getSelection().toString()", "The rebels.");
 
+    await waitForSecondaryPresentationUpdateIfNeeded();
+
     toggleOnlyShowTestContainer();
 }
 
@@ -133,6 +147,8 @@
     await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("downArrow", ["shiftKey", "ctrlKey"]) , document, "selectionchange");
     shouldBeEqualToString("window.getSelection().toString()", "Here's to the crazy ones.\n\nThe misfits.\n\nThe rebels.");
 
+    await waitForSecondaryPresentationUpdateIfNeeded();
+
     toggleOnlyShowTestContainer();
 }
 
@@ -146,6 +162,8 @@
     await UIHelper.callFunctionAndWaitForEvent(() => window.testRunner && UIHelper.keyDown("upArrow", ["shiftKey", "ctrlKey"]) , document, "selectionchange");
     shouldBeEqualToString("window.getSelection().toString()", "Here's to the crazy ones.\n\nThe misfits.");
 
+    await waitForSecondaryPresentationUpdateIfNeeded();
+
     toggleOnlyShowTestContainer();
 }
 

Modified: trunk/Source/WebCore/ChangeLog (249708 => 249709)


--- trunk/Source/WebCore/ChangeLog	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/Source/WebCore/ChangeLog	2019-09-10 10:53:59 UTC (rev 249709)
@@ -1,3 +1,50 @@
+2019-09-10  Ryosuke Niwa  <rn...@webkit.org>
+
+        Option + arrow moves caret past whitespace on iOS
+        https://bugs.webkit.org/show_bug.cgi?id=201575
+
+        Reviewed by Wenson Hsieh.
+
+        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.
+
 2019-09-09  Chris Dumez  <cdu...@apple.com>
 
         REGRESSION: http/tests/resourceLoadStatistics/do-not-capture-statistics-for-simple-top-navigations.html is frequently timing out on iOS EWS bots

Modified: trunk/Source/WebCore/editing/FrameSelection.cpp (249708 => 249709)


--- trunk/Source/WebCore/editing/FrameSelection.cpp	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/Source/WebCore/editing/FrameSelection.cpp	2019-09-10 10:53:59 UTC (rev 249709)
@@ -711,20 +711,26 @@
     return positionForPlatform(false);
 }
 
-VisiblePosition FrameSelection::nextWordPositionForPlatform(const VisiblePosition &originalPosition)
+static NextWordModeInIOS nextWordWhitespaceModeInIOS(EUserTriggered userTriggered)
 {
-    VisiblePosition positionAfterCurrentWord = nextWordPosition(originalPosition);
+    return userTriggered == UserTriggered ? NextWordModeInIOS::StopAfterWord : NextWordModeInIOS::LegacyStopBeforeWord;
+}
 
+VisiblePosition FrameSelection::nextWordPositionForPlatform(const VisiblePosition &originalPosition, EUserTriggered userTriggered)
+{
+    VisiblePosition positionAfterCurrentWord = nextWordPosition(originalPosition, nextWordWhitespaceModeInIOS(userTriggered));
+
     if (m_frame && m_frame->editor().behavior().shouldSkipSpaceWhenMovingRight()) {
         // In order to skip spaces when moving right, we advance one
         // word further and then move one word back. Given the
         // semantics of previousWordPosition() this will put us at the
         // beginning of the word following.
-        VisiblePosition positionAfterSpacingAndFollowingWord = nextWordPosition(positionAfterCurrentWord);
+        auto whitespaceMode = nextWordWhitespaceModeInIOS(userTriggered);
+        VisiblePosition positionAfterSpacingAndFollowingWord = nextWordPosition(positionAfterCurrentWord, whitespaceMode);
         if (positionAfterSpacingAndFollowingWord != positionAfterCurrentWord)
-            positionAfterCurrentWord = previousWordPosition(positionAfterSpacingAndFollowingWord);
+            positionAfterCurrentWord = previousWordPosition(positionAfterSpacingAndFollowingWord, whitespaceMode);
 
-        bool movingBackwardsMovedPositionToStartOfCurrentWord = positionAfterCurrentWord == previousWordPosition(nextWordPosition(originalPosition));
+        bool movingBackwardsMovedPositionToStartOfCurrentWord = positionAfterCurrentWord == previousWordPosition(nextWordPosition(originalPosition, whitespaceMode), whitespaceMode);
         if (movingBackwardsMovedPositionToStartOfCurrentWord)
             positionAfterCurrentWord = positionAfterSpacingAndFollowingWord;
     }
@@ -739,7 +745,7 @@
 }
 #endif
 
-VisiblePosition FrameSelection::modifyExtendingRight(TextGranularity granularity)
+VisiblePosition FrameSelection::modifyExtendingRight(TextGranularity granularity, EUserTriggered userTriggered)
 {
     VisiblePosition pos(m_selection.extent(), m_selection.affinity());
 
@@ -757,15 +763,15 @@
         break;
     case WordGranularity:
         if (directionOfEnclosingBlock() == TextDirection::LTR)
-            pos = nextWordPositionForPlatform(pos);
+            pos = nextWordPositionForPlatform(pos, userTriggered);
         else
-            pos = previousWordPosition(pos);
+            pos = previousWordPosition(pos, nextWordWhitespaceModeInIOS(userTriggered));
         break;
     case LineBoundary:
         if (directionOfEnclosingBlock() == TextDirection::LTR)
-            pos = modifyExtendingForward(granularity);
+            pos = modifyExtendingForward(granularity, userTriggered);
         else
-            pos = modifyExtendingBackward(granularity);
+            pos = modifyExtendingBackward(granularity, userTriggered);
         break;
     case SentenceGranularity:
     case LineGranularity:
@@ -774,7 +780,7 @@
     case ParagraphBoundary:
     case DocumentBoundary:
         // FIXME: implement all of the above?
-        pos = modifyExtendingForward(granularity);
+        pos = modifyExtendingForward(granularity, userTriggered);
         break;
     case DocumentGranularity:
         ASSERT_NOT_REACHED();
@@ -786,7 +792,7 @@
     return pos;
 }
 
-VisiblePosition FrameSelection::modifyExtendingForward(TextGranularity granularity)
+VisiblePosition FrameSelection::modifyExtendingForward(TextGranularity granularity, EUserTriggered userTriggered)
 {
     VisiblePosition pos(m_selection.extent(), m_selection.affinity());
     switch (granularity) {
@@ -794,7 +800,7 @@
         pos = pos.next(CannotCrossEditingBoundary);
         break;
     case WordGranularity:
-        pos = nextWordPositionForPlatform(pos);
+        pos = nextWordPositionForPlatform(pos, userTriggered);
         break;
     case SentenceGranularity:
         pos = nextSentencePosition(pos);
@@ -831,7 +837,7 @@
     return pos;
 }
 
-VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity, bool* reachedBoundary)
+VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity, EUserTriggered userTriggered, bool* reachedBoundary)
 {
     if (reachedBoundary)
         *reachedBoundary = false;
@@ -861,7 +867,7 @@
     case ParagraphBoundary:
     case DocumentBoundary:
         // FIXME: Implement all of the above.
-        pos = modifyMovingForward(granularity, reachedBoundary);
+        pos = modifyMovingForward(granularity, userTriggered, reachedBoundary);
         break;
     case LineBoundary:
         pos = rightBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock(), reachedBoundary);
@@ -873,7 +879,7 @@
     return pos;
 }
 
-VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity, bool* reachedBoundary)
+VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity, EUserTriggered userTriggered, bool* reachedBoundary)
 {
     if (reachedBoundary)
         *reachedBoundary = false;
@@ -903,7 +909,7 @@
             pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).next(CannotCrossEditingBoundary, reachedBoundary);
         break;
     case WordGranularity:
-        pos = nextWordPositionForPlatform(currentPosition);
+        pos = nextWordPositionForPlatform(currentPosition, userTriggered);
         break;
     case SentenceGranularity:
         pos = nextSentencePosition(currentPosition);
@@ -956,7 +962,7 @@
     return pos;
 }
 
-VisiblePosition FrameSelection::modifyExtendingLeft(TextGranularity granularity)
+VisiblePosition FrameSelection::modifyExtendingLeft(TextGranularity granularity, EUserTriggered userTriggered)
 {
     VisiblePosition pos(m_selection.extent(), m_selection.affinity());
 
@@ -974,15 +980,15 @@
         break;
     case WordGranularity:
         if (directionOfEnclosingBlock() == TextDirection::LTR)
-            pos = previousWordPosition(pos);
+            pos = previousWordPosition(pos, nextWordWhitespaceModeInIOS(userTriggered));
         else
-            pos = nextWordPositionForPlatform(pos);
+            pos = nextWordPositionForPlatform(pos, userTriggered);
         break;
     case LineBoundary:
         if (directionOfEnclosingBlock() == TextDirection::LTR)
-            pos = modifyExtendingBackward(granularity);
+            pos = modifyExtendingBackward(granularity, userTriggered);
         else
-            pos = modifyExtendingForward(granularity);
+            pos = modifyExtendingForward(granularity, userTriggered);
         break;
     case SentenceGranularity:
     case LineGranularity:
@@ -990,7 +996,7 @@
     case SentenceBoundary:
     case ParagraphBoundary:
     case DocumentBoundary:
-        pos = modifyExtendingBackward(granularity);
+        pos = modifyExtendingBackward(granularity, userTriggered);
         break;
     case DocumentGranularity:
         ASSERT_NOT_REACHED();
@@ -1002,7 +1008,7 @@
     return pos;
 }
        
-VisiblePosition FrameSelection::modifyExtendingBackward(TextGranularity granularity)
+VisiblePosition FrameSelection::modifyExtendingBackward(TextGranularity granularity, EUserTriggered userTriggered)
 {
     VisiblePosition pos(m_selection.extent(), m_selection.affinity());
 
@@ -1015,7 +1021,7 @@
         pos = pos.previous(CannotCrossEditingBoundary);
         break;
     case WordGranularity:
-        pos = previousWordPosition(pos);
+        pos = previousWordPosition(pos, nextWordWhitespaceModeInIOS(userTriggered));
         break;
     case SentenceGranularity:
         pos = previousSentencePosition(pos);
@@ -1052,7 +1058,7 @@
     return pos;
 }
 
-VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity, bool* reachedBoundary)
+VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity, EUserTriggered userTriggered, bool* reachedBoundary)
 {
     if (reachedBoundary)
         *reachedBoundary = false;
@@ -1082,7 +1088,7 @@
     case ParagraphBoundary:
     case DocumentBoundary:
         // FIXME: Implement all of the above.
-        pos = modifyMovingBackward(granularity, reachedBoundary);
+        pos = modifyMovingBackward(granularity, userTriggered, reachedBoundary);
         break;
     case LineBoundary:
         pos = leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock(), reachedBoundary);
@@ -1094,7 +1100,7 @@
     return pos;
 }
 
-VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity, bool* reachedBoundary)
+VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity, EUserTriggered userTriggered, bool* reachedBoundary)
 {
     if (reachedBoundary)
         *reachedBoundary = false;
@@ -1123,7 +1129,7 @@
             pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).previous(CannotCrossEditingBoundary, reachedBoundary);
         break;
     case WordGranularity:
-        pos = previousWordPosition(currentPosition);
+        pos = previousWordPosition(currentPosition, nextWordWhitespaceModeInIOS(userTriggered));
         break;
     case SentenceGranularity:
         pos = previousSentencePosition(currentPosition);
@@ -1330,27 +1336,27 @@
     switch (direction) {
     case DirectionRight:
         if (alter == AlterationMove)
-            position = modifyMovingRight(granularity, &reachedBoundary);
+            position = modifyMovingRight(granularity, userTriggered, &reachedBoundary);
         else
-            position = modifyExtendingRight(granularity);
+            position = modifyExtendingRight(granularity, userTriggered);
         break;
     case DirectionForward:
         if (alter == AlterationExtend)
-            position = modifyExtendingForward(granularity);
+            position = modifyExtendingForward(granularity, userTriggered);
         else
-            position = modifyMovingForward(granularity, &reachedBoundary);
+            position = modifyMovingForward(granularity, userTriggered, &reachedBoundary);
         break;
     case DirectionLeft:
         if (alter == AlterationMove)
-            position = modifyMovingLeft(granularity, &reachedBoundary);
+            position = modifyMovingLeft(granularity, userTriggered, &reachedBoundary);
         else
-            position = modifyExtendingLeft(granularity);
+            position = modifyExtendingLeft(granularity, userTriggered);
         break;
     case DirectionBackward:
         if (alter == AlterationExtend)
-            position = modifyExtendingBackward(granularity);
+            position = modifyExtendingBackward(granularity, userTriggered);
         else
-            position = modifyMovingBackward(granularity, &reachedBoundary);
+            position = modifyMovingBackward(granularity, userTriggered, &reachedBoundary);
         break;
     }
 
@@ -2145,7 +2151,7 @@
     // Construct a new VisibleSolution, since m_selection is not necessarily valid, and the following steps
     // assume a valid selection. See <https://bugs.webkit.org/show_bug.cgi?id=69563> and <rdar://problem/10232866>.
 #if ENABLE(TEXT_CARET)
-    VisiblePosition endVisiblePosition = paintBlockCursor ? modifyExtendingForward(CharacterGranularity) : oldSelection.visibleEnd();
+    VisiblePosition endVisiblePosition = paintBlockCursor ? modifyExtendingForward(CharacterGranularity, NotUserTriggered) : oldSelection.visibleEnd();
     VisibleSelection selection(oldSelection.visibleStart(), endVisiblePosition);
 #else
     VisibleSelection selection(oldSelection.visibleStart(), oldSelection.visibleEnd());

Modified: trunk/Source/WebCore/editing/FrameSelection.h (249708 => 249709)


--- trunk/Source/WebCore/editing/FrameSelection.h	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/Source/WebCore/editing/FrameSelection.h	2019-09-10 10:53:59 UTC (rev 249709)
@@ -299,16 +299,16 @@
     VisiblePosition positionForPlatform(bool isGetStart) const;
     VisiblePosition startForPlatform() const;
     VisiblePosition endForPlatform() const;
-    VisiblePosition nextWordPositionForPlatform(const VisiblePosition&);
+    VisiblePosition nextWordPositionForPlatform(const VisiblePosition&, EUserTriggered);
 
-    VisiblePosition modifyExtendingRight(TextGranularity);
-    VisiblePosition modifyExtendingForward(TextGranularity);
-    VisiblePosition modifyMovingRight(TextGranularity, bool* reachedBoundary = nullptr);
-    VisiblePosition modifyMovingForward(TextGranularity, bool* reachedBoundary = nullptr);
-    VisiblePosition modifyExtendingLeft(TextGranularity);
-    VisiblePosition modifyExtendingBackward(TextGranularity);
-    VisiblePosition modifyMovingLeft(TextGranularity, bool* reachedBoundary = nullptr);
-    VisiblePosition modifyMovingBackward(TextGranularity, bool* reachedBoundary = nullptr);
+    VisiblePosition modifyExtendingRight(TextGranularity, EUserTriggered);
+    VisiblePosition modifyExtendingForward(TextGranularity, EUserTriggered);
+    VisiblePosition modifyMovingRight(TextGranularity, EUserTriggered, bool* reachedBoundary = nullptr);
+    VisiblePosition modifyMovingForward(TextGranularity, EUserTriggered, bool* reachedBoundary = nullptr);
+    VisiblePosition modifyExtendingLeft(TextGranularity, EUserTriggered);
+    VisiblePosition modifyExtendingBackward(TextGranularity, EUserTriggered);
+    VisiblePosition modifyMovingLeft(TextGranularity, EUserTriggered, bool* reachedBoundary = nullptr);
+    VisiblePosition modifyMovingBackward(TextGranularity, EUserTriggered, bool* reachedBoundary = nullptr);
 
     LayoutUnit lineDirectionPointForBlockDirectionNavigation(EPositionType);
 

Modified: trunk/Source/WebCore/editing/TextIterator.cpp (249708 => 249709)


--- trunk/Source/WebCore/editing/TextIterator.cpp	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/Source/WebCore/editing/TextIterator.cpp	2019-09-10 10:53:59 UTC (rev 249709)
@@ -2335,7 +2335,7 @@
 
     size_t wordBreakSearchStart = start + length;
     while (wordBreakSearchStart > start)
-        wordBreakSearchStart = findNextWordFromIndex(StringView(m_buffer.data(), m_buffer.size()), wordBreakSearchStart, false /* backwards */);
+        wordBreakSearchStart = findNextWordFromIndex(StringView(m_buffer.data(), m_buffer.size()), wordBreakSearchStart, NextWordDirection::Backward);
     return wordBreakSearchStart == start;
 }
 

Modified: trunk/Source/WebCore/editing/VisibleUnits.cpp (249708 => 249709)


--- trunk/Source/WebCore/editing/VisibleUnits.cpp	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/Source/WebCore/editing/VisibleUnits.cpp	2019-09-10 10:53:59 UTC (rev 249709)
@@ -761,6 +761,7 @@
     return nextBoundary(p, endWordBoundary);
 }
 
+template <NextWordModeInIOS nextWordModeInIOS>
 static unsigned previousWordPositionBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
 {
     if (mayHaveMoreContext && !startOfLastWordBoundaryContext(text.substring(0, offset))) {
@@ -768,14 +769,17 @@
         return 0;
     }
     needMoreContext = false;
-    return findNextWordFromIndex(text, offset, false);
+    return findNextWordFromIndex(text, offset, NextWordDirection::Backward, nextWordModeInIOS);
 }
 
-VisiblePosition previousWordPosition(const VisiblePosition& position)
+VisiblePosition previousWordPosition(const VisiblePosition& position, NextWordModeInIOS nextWordModeInIOS)
 {
-    return position.honorEditingBoundaryAtOrBefore(previousBoundary(position, previousWordPositionBoundary));
+    if (nextWordModeInIOS == NextWordModeInIOS::LegacyStopBeforeWord) // FIXME: Remove this code path.
+        return position.honorEditingBoundaryAtOrBefore(previousBoundary(position, previousWordPositionBoundary<NextWordModeInIOS::LegacyStopBeforeWord>));
+    return position.honorEditingBoundaryAtOrBefore(previousBoundary(position, previousWordPositionBoundary<NextWordModeInIOS::StopAfterWord>));
 }
 
+template <NextWordModeInIOS nextWordModeInIOS>
 static unsigned nextWordPositionBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
 {
     if (mayHaveMoreContext && endOfFirstWordBoundaryContext(text.substring(offset)) == text.length() - offset) {
@@ -783,12 +787,14 @@
         return text.length();
     }
     needMoreContext = false;
-    return findNextWordFromIndex(text, offset, true);
+    return findNextWordFromIndex(text, offset, NextWordDirection::Forward, nextWordModeInIOS);
 }
 
-VisiblePosition nextWordPosition(const VisiblePosition& position)
+VisiblePosition nextWordPosition(const VisiblePosition& position, NextWordModeInIOS nextWordModeInIOS)
 {
-    return position.honorEditingBoundaryAtOrAfter(nextBoundary(position, nextWordPositionBoundary));
+    if (nextWordModeInIOS == NextWordModeInIOS::LegacyStopBeforeWord) // FIXME: Remove this code path.
+        return position.honorEditingBoundaryAtOrAfter(nextBoundary(position, nextWordPositionBoundary<NextWordModeInIOS::LegacyStopBeforeWord>));
+    return position.honorEditingBoundaryAtOrAfter(nextBoundary(position, nextWordPositionBoundary<NextWordModeInIOS::StopAfterWord>));
 }
 
 bool isStartOfWord(const VisiblePosition& p)

Modified: trunk/Source/WebCore/editing/VisibleUnits.h (249708 => 249709)


--- trunk/Source/WebCore/editing/VisibleUnits.h	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/Source/WebCore/editing/VisibleUnits.h	2019-09-10 10:53:59 UTC (rev 249709)
@@ -26,6 +26,7 @@
 #pragma once
 
 #include "EditingBoundary.h"
+#include "TextBoundaries.h"
 #include "VisibleSelection.h"
 
 namespace WebCore {
@@ -40,8 +41,8 @@
 // words
 WEBCORE_EXPORT VisiblePosition startOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBoundary);
 WEBCORE_EXPORT VisiblePosition endOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBoundary);
-WEBCORE_EXPORT VisiblePosition previousWordPosition(const VisiblePosition &);
-WEBCORE_EXPORT VisiblePosition nextWordPosition(const VisiblePosition &);
+WEBCORE_EXPORT VisiblePosition previousWordPosition(const VisiblePosition&, NextWordModeInIOS = NextWordModeInIOS::LegacyStopBeforeWord);
+WEBCORE_EXPORT VisiblePosition nextWordPosition(const VisiblePosition&, NextWordModeInIOS = NextWordModeInIOS::LegacyStopBeforeWord);
 VisiblePosition rightWordPosition(const VisiblePosition&, bool skipsSpaceWhenMovingRight);
 VisiblePosition leftWordPosition(const VisiblePosition&, bool skipsSpaceWhenMovingRight);
 bool isStartOfWord(const VisiblePosition&);

Modified: trunk/Source/WebCore/platform/text/TextBoundaries.cpp (249708 => 249709)


--- trunk/Source/WebCore/platform/text/TextBoundaries.cpp	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/Source/WebCore/platform/text/TextBoundaries.cpp	2019-09-10 10:53:59 UTC (rev 249709)
@@ -61,11 +61,11 @@
 
 #if !PLATFORM(COCOA)
 
-int findNextWordFromIndex(StringView text, int position, bool forward)
+int findNextWordFromIndex(StringView text, int position, NextWordDirection direction, NextWordModeInIOS)
 {
     UBreakIterator* it = wordBreakIterator(text);
 
-    if (forward) {
+    if (direction == NextWordDirection::Forward) {
         position = ubrk_following(it, position);
         while (position != UBRK_DONE) {
             // We stop searching when the character preceeding the break is alphanumeric.

Modified: trunk/Source/WebCore/platform/text/TextBoundaries.h (249708 => 249709)


--- trunk/Source/WebCore/platform/text/TextBoundaries.h	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/Source/WebCore/platform/text/TextBoundaries.h	2019-09-10 10:53:59 UTC (rev 249709)
@@ -47,8 +47,11 @@
 
     void findWordBoundary(StringView, int position, int* start, int* end);
     void findEndWordBoundary(StringView, int position, int* end);
-    int findNextWordFromIndex(StringView, int position, bool forward);
 
+    enum class NextWordDirection : bool { Forward, Backward };
+    enum class NextWordModeInIOS : bool { LegacyStopBeforeWord, StopAfterWord };
+    int findNextWordFromIndex(StringView, int position, NextWordDirection, NextWordModeInIOS = NextWordModeInIOS::LegacyStopBeforeWord);
+
 }
 
 #endif

Modified: trunk/Source/WebCore/platform/text/mac/TextBoundaries.mm (249708 => 249709)


--- trunk/Source/WebCore/platform/text/mac/TextBoundaries.mm	2019-09-10 09:42:15 UTC (rev 249708)
+++ trunk/Source/WebCore/platform/text/mac/TextBoundaries.mm	2019-09-10 10:53:59 UTC (rev 249709)
@@ -229,11 +229,12 @@
     findWordBoundary(text, position, &start, end);
 }
 
-int findNextWordFromIndex(StringView text, int position, bool forward)
+int findNextWordFromIndex(StringView text, int position, NextWordDirection direction, NextWordModeInIOS whitespaceModeInIOS)
 {   
 #if USE(APPKIT)
+    UNUSED_PARAM(whitespaceModeInIOS);
     NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:text.createNSStringWithoutCopying().get()];
-    int result = [attributedString nextWordFromIndex:position forward:forward];
+    int result = [attributedString nextWordFromIndex:position forward:direction == NextWordDirection::Forward];
     [attributedString release];
     return result;
 #else
@@ -243,14 +244,31 @@
     int pos = position;
     UBreakIterator* boundary = wordBreakIterator(text);
     if (boundary) {
-        if (forward) {
+        if (direction == NextWordDirection::Forward) {
+            if (whitespaceModeInIOS == NextWordModeInIOS::StopAfterWord) {
+                while (static_cast<unsigned>(pos) < text.length() && isWordDelimitingCharacter(text[pos])) {
+                    pos = ubrk_following(boundary, pos);
+                    if (pos == UBRK_DONE)
+                        return text.length();
+                }
+            }
             do {
                 pos = ubrk_following(boundary, pos);
                 if (pos == UBRK_DONE)
-                    pos = text.length();
+                    return text.length();
             } while (static_cast<unsigned>(pos) < text.length() && (pos == 0 || !isSkipCharacter(text[pos - 1])) && isSkipCharacter(text[pos]));
+
+            // ICU would skip the trailing whitespace. Go back.
+            if (whitespaceModeInIOS == NextWordModeInIOS::StopAfterWord && isWordDelimitingCharacter(text[pos - 1]))
+                pos = ubrk_preceding(boundary, pos);
         }
         else {
+            if (whitespaceModeInIOS == NextWordModeInIOS::StopAfterWord && pos && isWordDelimitingCharacter(text[pos - 1])) {
+                pos = ubrk_preceding(boundary, pos);
+                if (pos == UBRK_DONE)
+                    return 0;
+            }
+
             do {
                 pos = ubrk_preceding(boundary, pos);
                 if (pos == UBRK_DONE)
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to