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)