Skip to content

Commit

Permalink
[UIAsyncTextInput] Adopt APIs to delete, move and extend the selection
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=264711
rdar://118304457

Reviewed by Aditya Keerthi.

Adopt the following methods for moving or extending the selection, or deleting text:

```
@protocol UIAsyncTextInput
- (void)deleteInDirection:(UITextStorageDirection)direction toGranularity:(UITextGranularity)granularity;
- (void)moveInDirection:(UITextStorageDirection)direction byGranularity:(UITextGranularity)granularity;
- (void)extendInDirection:(UITextStorageDirection)direction byGranularity:(UITextGranularity)granularity;
- (void)moveInLayoutDirection:(UITextLayoutDirection)direction;
- (void)extendInLayoutDirection:(UITextLayoutDirection)direction;
@EnD
```

This takes the place of the following SPI and IPI selectors in UIKit:

```
-_deleteByWord
-_deleteForwardByWord
-_deleteToStartOfLine
-_deleteToEndOfLine
-_deleteToEndOfParagraph
-_deleteForwardAndNotify:
-_moveUp:withHistory:
-_moveDown:withHistory:
-_moveLeft:withHistory:
-_moveRight:withHistory:
-_moveToStartOfWord:withHistory:
-_moveToStartOfParagraph:withHistory:
-_moveToStartOfLine:withHistory:
-_moveToStartOfDocument:withHistory:
-_moveToEndOfWord:withHistory:
-_moveToEndOfParagraph:withHistory:
-_moveToEndOfLine:withHistory:
-_moveToEndOfDocument:withHistory:
```

* Source/WebKit/Platform/spi/ios/UIKitSPI.h:

Add staging declarations for the new API methods.

* Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView canPerformAction:withSender:]):
(-[WKContentView cutForWebView:]):
(-[WKContentView toggleBoldfaceForWebView:]):
(-[WKContentView toggleItalicsForWebView:]):
(-[WKContentView toggleUnderlineForWebView:]):
(-[WKContentView _executeEditCommand:]):

Rename `-executeEditCommandWithCallback:` to `-_executeEditCommand:`; the existing name is a bit
misleading, since it doesn't take a callback.

(-[WKContentView _deleteByWord]):
(-[WKContentView _deleteForwardByWord]):
(-[WKContentView _deleteToStartOfLine]):
(-[WKContentView _deleteToEndOfLine]):
(-[WKContentView _deleteForwardAndNotify:]):
(-[WKContentView _deleteToEndOfParagraph]):
(-[WKContentView _transpose]):
(-[WKContentView _moveUp:withHistory:]):
(-[WKContentView _moveDown:withHistory:]):
(-[WKContentView _moveLeft:withHistory:]):
(-[WKContentView _moveRight:withHistory:]):
(-[WKContentView _moveToStartOfWord:withHistory:]):
(-[WKContentView _moveToStartOfParagraph:withHistory:]):
(-[WKContentView _moveToStartOfLine:withHistory:]):
(-[WKContentView _moveToStartOfDocument:withHistory:]):
(-[WKContentView _moveToEndOfWord:withHistory:]):
(-[WKContentView _moveToEndOfParagraph:withHistory:]):
(-[WKContentView _moveToEndOfLine:withHistory:]):
(-[WKContentView _moveToEndOfDocument:withHistory:]):
(moveSelectionCommand):
(extendSelectionCommand):

Add helper methods to return a command (or list of commands) to execute, given `UITextGranularity`,
`UITextStorageDirection` and `UITextLayoutDirection`.

(-[WKContentView deleteInDirection:toGranularity:]):
(-[WKContentView moveInDirection:byGranularity:]):
(-[WKContentView moveInLayoutDirection:]):
(-[WKContentView extendInDirection:byGranularity:]):
(-[WKContentView extendInLayoutDirection:]):
(-[WKContentView executeEditCommandWithCallback:]): Deleted.

Canonical link: https://commits.webkit.org/270671@main
  • Loading branch information
whsieh committed Nov 13, 2023
1 parent 5ea2b90 commit a81c6d7
Show file tree
Hide file tree
Showing 2 changed files with 215 additions and 36 deletions.
10 changes: 10 additions & 0 deletions Source/WebKit/Platform/spi/ios/UIKitSPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,16 @@ typedef NS_ENUM(NSUInteger, _UIScrollDeviceCategory) {

@end

@protocol UIAsyncTextInput_Staging_117155812 <UIAsyncTextInput>

- (void)deleteInDirection:(UITextStorageDirection)direction toGranularity:(UITextGranularity)granularity;
- (void)moveInDirection:(UITextStorageDirection)direction byGranularity:(UITextGranularity)granularity;
- (void)extendInDirection:(UITextStorageDirection)direction byGranularity:(UITextGranularity)granularity;
- (void)moveInLayoutDirection:(UITextLayoutDirection)direction;
- (void)extendInLayoutDirection:(UITextLayoutDirection)direction;

@end

#endif // HAVE(UI_ASYNC_TEXT_INTERACTION)

WTF_EXTERN_C_BEGIN
Expand Down
241 changes: 205 additions & 36 deletions Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Original file line number Diff line number Diff line change
Expand Up @@ -4136,17 +4136,29 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
if (_domPasteRequestHandler)
return action == @selector(paste:);

// These are UIKit IPI selectors. We don't want to forward them to the web view.
auto editorState = _page->editorState();
if (action == @selector(_moveDown:withHistory:) || action == @selector(_moveLeft:withHistory:) || action == @selector(_moveRight:withHistory:)
|| action == @selector(_moveToEndOfDocument:withHistory:) || action == @selector(_moveToEndOfLine:withHistory:) || action == @selector(_moveToEndOfParagraph:withHistory:)
|| action == @selector(_moveToEndOfWord:withHistory:) || action == @selector(_moveToStartOfDocument:withHistory:) || action == @selector(_moveToStartOfLine:withHistory:)
|| action == @selector(_moveToStartOfParagraph:withHistory:) || action == @selector(_moveToStartOfWord:withHistory:) || action == @selector(_moveUp:withHistory:))
return !editorState.selectionIsNone;
auto& editorState = _page->editorState();
#if HAVE(UI_ASYNC_TEXT_INTERACTION)
if (self.shouldUseAsyncInteractions) {
if (action == @selector(moveInDirection:byGranularity:) || action == @selector(moveInLayoutDirection:)
|| action == @selector(extendInDirection:byGranularity:) || action == @selector(extendInLayoutDirection:))
return !editorState.selectionIsNone;

if (action == @selector(deleteInDirection:toGranularity:))
return editorState.isContentEditable;
} else
#endif // HAVE(UI_ASYNC_TEXT_INTERACTION)
{
// These are UIKit IPI selectors. We don't want to forward them to the web view.
if (action == @selector(_moveDown:withHistory:) || action == @selector(_moveLeft:withHistory:) || action == @selector(_moveRight:withHistory:)
|| action == @selector(_moveToEndOfDocument:withHistory:) || action == @selector(_moveToEndOfLine:withHistory:) || action == @selector(_moveToEndOfParagraph:withHistory:)
|| action == @selector(_moveToEndOfWord:withHistory:) || action == @selector(_moveToStartOfDocument:withHistory:) || action == @selector(_moveToStartOfLine:withHistory:)
|| action == @selector(_moveToStartOfParagraph:withHistory:) || action == @selector(_moveToStartOfWord:withHistory:) || action == @selector(_moveUp:withHistory:))
return !editorState.selectionIsNone;

if (action == @selector(_deleteByWord) || action == @selector(_deleteForwardByWord) || action == @selector(_deleteForwardAndNotify:) || action == @selector(_deleteToEndOfParagraph) || action == @selector(_deleteToStartOfLine)
|| action == @selector(_transpose))
return editorState.isContentEditable;
if (action == @selector(_deleteByWord) || action == @selector(_deleteForwardByWord) || action == @selector(_deleteForwardAndNotify:)
|| action == @selector(_deleteToEndOfParagraph) || action == @selector(_deleteToStartOfLine) || action == @selector(_transpose))
return editorState.isContentEditable;
}

return [_webView canPerformAction:action withSender:sender];
}
Expand Down Expand Up @@ -4376,7 +4388,7 @@ - (void)copyForWebView:(id)sender

- (void)cutForWebView:(id)sender
{
[self executeEditCommandWithCallback:@"cut"];
[self _executeEditCommand:@"cut"];
}

- (void)pasteForWebView:(id)sender
Expand Down Expand Up @@ -4429,7 +4441,7 @@ - (void)toggleBoldfaceForWebView:(id)sender
if (!_page->editorState().isContentRichlyEditable)
return;

[self executeEditCommandWithCallback:@"toggleBold"];
[self _executeEditCommand:@"toggleBold"];

if (self.shouldSynthesizeKeyEvents)
_page->generateSyntheticEditingCommand(WebKit::SyntheticEditingCommandType::ToggleBoldface);
Expand All @@ -4440,7 +4452,7 @@ - (void)toggleItalicsForWebView:(id)sender
if (!_page->editorState().isContentRichlyEditable)
return;

[self executeEditCommandWithCallback:@"toggleItalic"];
[self _executeEditCommand:@"toggleItalic"];

if (self.shouldSynthesizeKeyEvents)
_page->generateSyntheticEditingCommand(WebKit::SyntheticEditingCommandType::ToggleItalic);
Expand All @@ -4451,7 +4463,7 @@ - (void)toggleUnderlineForWebView:(id)sender
if (!_page->editorState().isContentRichlyEditable)
return;

[self executeEditCommandWithCallback:@"toggleUnderline"];
[self _executeEditCommand:@"toggleUnderline"];

if (self.shouldSynthesizeKeyEvents)
_page->generateSyntheticEditingCommand(WebKit::SyntheticEditingCommandType::ToggleUnderline);
Expand Down Expand Up @@ -6962,7 +6974,7 @@ - (BOOL)isKeyboardScrollingAnimationRunning
return _isKeyboardScrollingAnimationRunning;
}

- (void)executeEditCommandWithCallback:(NSString *)commandName
- (void)_executeEditCommand:(NSString *)commandName
{
_autocorrectionContextNeedsUpdate = YES;
// FIXME: Editing commands are not considered by WebKit as user initiated even if they are the result
Expand All @@ -6977,110 +6989,146 @@ - (void)executeEditCommandWithCallback:(NSString *)commandName

- (void)_deleteByWord
{
[self executeEditCommandWithCallback:@"deleteWordBackward"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:@"deleteWordBackward"];
}

- (void)_deleteForwardByWord
{
[self executeEditCommandWithCallback:@"deleteWordForward"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:@"deleteWordForward"];
}

- (void)_deleteToStartOfLine
{
[self executeEditCommandWithCallback:@"deleteToBeginningOfLine"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:@"deleteToBeginningOfLine"];
}

- (void)_deleteToEndOfLine
{
[self executeEditCommandWithCallback:@"deleteToEndOfLine"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:@"deleteToEndOfLine"];
}

- (void)_deleteForwardAndNotify:(BOOL)notify
{
[self executeEditCommandWithCallback:@"deleteForward"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:@"deleteForward"];
}

- (void)_deleteToEndOfParagraph
{
[self executeEditCommandWithCallback:@"deleteToEndOfParagraph"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:@"deleteToEndOfParagraph"];
}

- (void)_transpose
{
[self executeEditCommandWithCallback:@"transpose"];
[self _executeEditCommand:@"transpose"];
}

- (UITextInputArrowKeyHistory *)_moveUp:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveUpAndModifySelection" : @"moveUp"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveUpAndModifySelection" : @"moveUp"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveDown:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveDownAndModifySelection" : @"moveDown"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveDownAndModifySelection" : @"moveDown"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveLeft:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending? @"moveLeftAndModifySelection" : @"moveLeft"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending? @"moveLeftAndModifySelection" : @"moveLeft"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveRight:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveRightAndModifySelection" : @"moveRight"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveRightAndModifySelection" : @"moveRight"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveToStartOfWord:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveWordBackwardAndModifySelection" : @"moveWordBackward"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveWordBackwardAndModifySelection" : @"moveWordBackward"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveToStartOfParagraph:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveBackwardAndModifySelection" : @"moveBackward"];
[self executeEditCommandWithCallback:extending ? @"moveToBeginningOfParagraphAndModifySelection" : @"moveToBeginningOfParagraph"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveBackwardAndModifySelection" : @"moveBackward"];
[self _executeEditCommand:extending ? @"moveToBeginningOfParagraphAndModifySelection" : @"moveToBeginningOfParagraph"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveToStartOfLine:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveToBeginningOfLineAndModifySelection" : @"moveToBeginningOfLine"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveToBeginningOfLineAndModifySelection" : @"moveToBeginningOfLine"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveToStartOfDocument:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveToBeginningOfDocumentAndModifySelection" : @"moveToBeginningOfDocument"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveToBeginningOfDocumentAndModifySelection" : @"moveToBeginningOfDocument"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveToEndOfWord:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveWordForwardAndModifySelection" : @"moveWordForward"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveWordForwardAndModifySelection" : @"moveWordForward"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveToEndOfParagraph:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveForwardAndModifySelection" : @"moveForward"];
[self executeEditCommandWithCallback:extending ? @"moveToEndOfParagraphAndModifySelection" : @"moveToEndOfParagraph"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveForwardAndModifySelection" : @"moveForward"];
[self _executeEditCommand:extending ? @"moveToEndOfParagraphAndModifySelection" : @"moveToEndOfParagraph"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveToEndOfLine:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveToEndOfLineAndModifySelection" : @"moveToEndOfLine"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveToEndOfLineAndModifySelection" : @"moveToEndOfLine"];
return nil;
}

- (UITextInputArrowKeyHistory *)_moveToEndOfDocument:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
{
[self executeEditCommandWithCallback:extending ? @"moveToEndOfDocumentAndModifySelection" : @"moveToEndOfDocument"];
RELEASE_ASSERT_ASYNC_TEXT_INTERACTIONS_DISABLED();

[self _executeEditCommand:extending ? @"moveToEndOfDocumentAndModifySelection" : @"moveToEndOfDocument"];
return nil;
}

Expand Down Expand Up @@ -12133,6 +12181,127 @@ - (CGRect)selectionClipRect
return [self _selectionClipRectInternal];
}

inline static NSArray<NSString *> *deleteSelectionCommands(UITextStorageDirection direction, UITextGranularity granularity)
{
BOOL backward = direction == UITextStorageDirectionBackward;
switch (granularity) {
case UITextGranularityCharacter:
return @[ backward ? @"DeleteBackward" : @"DeleteForward" ];
case UITextGranularityWord:
return @[ backward ? @"DeleteWordBackward" : @"DeleteWordForward" ];
case UITextGranularitySentence:
return @[ backward ? @"MoveToBeginningOfSentenceAndModifySelection" : @"MoveToEndOfSentenceAndModifySelection", @"DeleteBackward" ];
case UITextGranularityParagraph:
return @[ backward ? @"DeleteToBeginningOfParagraph" : @"DeleteToEndOfParagraph" ];
case UITextGranularityLine:
return @[ backward ? @"DeleteToBeginningOfLine" : @"DeleteToEndOfLine" ];
case UITextGranularityDocument:
return @[ backward ? @"MoveToBeginningOfDocumentAndModifySelection" : @"MoveToEndOfDocumentAndModifySelection", @"DeleteBackward" ];
}
ASSERT_NOT_REACHED();
return @[ ];
}

inline static NSString *moveSelectionCommand(UITextStorageDirection direction, UITextGranularity granularity)
{
BOOL backward = direction == UITextStorageDirectionBackward;
switch (granularity) {
case UITextGranularityCharacter:
return backward ? @"MoveBackward" : @"MoveForward";
case UITextGranularityWord:
return backward ? @"MoveWordBackward" : @"MoveWordForward";
case UITextGranularitySentence:
return backward ? @"MoveToBeginningOfSentence" : @"MoveToEndOfSentence";
case UITextGranularityParagraph:
return backward ? @"MoveToBeginningOfParagraph" : @"MoveToEndOfParagraph";
case UITextGranularityLine:
return backward ? @"MoveToBeginningOfLine" : @"MoveToEndOfLine";
case UITextGranularityDocument:
return backward ? @"MoveToBeginningOfDocument" : @"MoveToEndOfDocument";
}
ASSERT_NOT_REACHED();
return nil;
}

inline static NSString *extendSelectionCommand(UITextStorageDirection direction, UITextGranularity granularity)
{
BOOL backward = direction == UITextStorageDirectionBackward;
switch (granularity) {
case UITextGranularityCharacter:
return backward ? @"MoveBackwardAndModifySelection" : @"MoveForwardAndModifySelection";
case UITextGranularityWord:
return backward ? @"MoveWordBackwardAndModifySelection" : @"MoveWordForwardAndModifySelection";
case UITextGranularitySentence:
return backward ? @"MoveToBeginningOfSentenceAndModifySelection" : @"MoveToEndOfSentenceAndModifySelection";
case UITextGranularityParagraph:
return backward ? @"MoveToBeginningOfParagraphAndModifySelection" : @"MoveToEndOfParagraphAndModifySelection";
case UITextGranularityLine:
return backward ? @"MoveToBeginningOfLineAndModifySelection" : @"MoveToEndOfLineAndModifySelection";
case UITextGranularityDocument:
return backward ? @"MoveToBeginningOfDocumentAndModifySelection" : @"MoveToEndOfDocumentAndModifySelection";
}
ASSERT_NOT_REACHED();
return nil;
}

inline static NSString *moveSelectionCommand(UITextLayoutDirection direction)
{
switch (direction) {
case UITextLayoutDirectionRight:
return @"MoveRight";
case UITextLayoutDirectionLeft:
return @"MoveLeft";
case UITextLayoutDirectionUp:
return @"MoveUp";
case UITextLayoutDirectionDown:
return @"MoveDown";
}
ASSERT_NOT_REACHED();
return nil;
}

inline static NSString *extendSelectionCommand(UITextLayoutDirection direction)
{
switch (direction) {
case UITextLayoutDirectionRight:
return @"MoveRightAndModifySelection";
case UITextLayoutDirectionLeft:
return @"MoveLeftAndModifySelection";
case UITextLayoutDirectionUp:
return @"MoveUpAndModifySelection";
case UITextLayoutDirectionDown:
return @"MoveDownAndModifySelection";
}
ASSERT_NOT_REACHED();
return nil;
}

- (void)deleteInDirection:(UITextStorageDirection)direction toGranularity:(UITextGranularity)granularity
{
for (NSString *command in deleteSelectionCommands(direction, granularity))
[self _executeEditCommand:command];
}

- (void)moveInDirection:(UITextStorageDirection)direction byGranularity:(UITextGranularity)granularity
{
[self _executeEditCommand:moveSelectionCommand(direction, granularity)];
}

- (void)moveInLayoutDirection:(UITextLayoutDirection)direction
{
[self _executeEditCommand:moveSelectionCommand(direction)];
}

- (void)extendInDirection:(UITextStorageDirection)direction byGranularity:(UITextGranularity)granularity
{
[self _executeEditCommand:extendSelectionCommand(direction, granularity)];
}

- (void)extendInLayoutDirection:(UITextLayoutDirection)direction
{
[self _executeEditCommand:extendSelectionCommand(direction)];
}

#endif // HAVE(UI_ASYNC_TEXT_INTERACTION)

#pragma mark - UIAsyncTextInteractionDelegate
Expand Down

0 comments on commit a81c6d7

Please sign in to comment.