[iOS WebKit2] support replacements for misspelled words.
authorenrica@apple.com <enrica@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 18 Apr 2014 00:01:59 +0000 (00:01 +0000)
committerenrica@apple.com <enrica@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 18 Apr 2014 00:01:59 +0000 (00:01 +0000)
https://bugs.webkit.org/show_bug.cgi?id=131827
<rdar://problem/16319657>

Reviewed by Benjamin Poulain.

This is the first part of the work to add support for replacements.
It handles _promptForReplace in canPerformAction to decide
whether to show the Replace button in the menu and add the
implementation of the replace action.

* Shared/EditorState.cpp:
(WebKit::EditorState::encode):
(WebKit::EditorState::decode):
* Shared/EditorState.h:
(WebKit::EditorState::EditorState):
* UIProcess/WebPageProxy.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView selectedText]):
(-[WKContentView replaceText:withText:]):
(-[WKContentView _promptForReplace:]):
(-[WKContentView replace:]):
(-[WKContentView canPerformAction:withSender:]):
(-[WKContentView selectWordForReplacement]): This is called
by UIKit when the user taps on a mispelled word to select it.
* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::replaceSelectedText):
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::WebPage):
(WebKit::WebPage::editorState):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::selectWithGesture): Removed m_shouldReturnWordForSelection.
We now return always the word corresponding to the caret selection or
the selected text up to a maximum of 200 characters.
(WebKit::WebPage::extendSelection):
(WebKit::WebPage::replaceSelectedText):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@167469 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit2/ChangeLog
Source/WebKit2/Shared/EditorState.cpp
Source/WebKit2/Shared/EditorState.h
Source/WebKit2/UIProcess/WebPageProxy.h
Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit2/UIProcess/ios/WebPageProxyIOS.mm
Source/WebKit2/WebProcess/WebPage/WebPage.cpp
Source/WebKit2/WebProcess/WebPage/WebPage.h
Source/WebKit2/WebProcess/WebPage/WebPage.messages.in
Source/WebKit2/WebProcess/WebPage/ios/WebPageIOS.mm

index 5945478..eea4e0b 100644 (file)
@@ -1,3 +1,44 @@
+2014-04-17  Enrica Casucci  <enrica@apple.com>
+
+        [iOS WebKit2] support replacements for misspelled words.
+        https://bugs.webkit.org/show_bug.cgi?id=131827
+        <rdar://problem/16319657>
+
+        Reviewed by Benjamin Poulain.
+
+        This is the first part of the work to add support for replacements.
+        It handles _promptForReplace in canPerformAction to decide
+        whether to show the Replace button in the menu and add the
+        implementation of the replace action.
+
+        * Shared/EditorState.cpp:
+        (WebKit::EditorState::encode):
+        (WebKit::EditorState::decode):
+        * Shared/EditorState.h:
+        (WebKit::EditorState::EditorState):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView selectedText]):
+        (-[WKContentView replaceText:withText:]):
+        (-[WKContentView _promptForReplace:]):
+        (-[WKContentView replace:]):
+        (-[WKContentView canPerformAction:withSender:]):
+        (-[WKContentView selectWordForReplacement]): This is called
+        by UIKit when the user taps on a mispelled word to select it.
+        * UIProcess/ios/WebPageProxyIOS.mm:
+        (WebKit::WebPageProxy::replaceSelectedText):
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::WebPage):
+        (WebKit::WebPage::editorState):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::selectWithGesture): Removed m_shouldReturnWordForSelection.
+        We now return always the word corresponding to the caret selection or
+        the selected text up to a maximum of 200 characters.
+        (WebKit::WebPage::extendSelection):
+        (WebKit::WebPage::replaceSelectedText):
+
 2014-04-17  Tim Horton  <timothy_horton@apple.com>
 
         [iOS] REGRESSION (r166975): WKPDFView is broken
index 3593d7b..d58c2a4 100644 (file)
@@ -47,6 +47,7 @@ void EditorState::encode(IPC::ArgumentEncoder& encoder) const
     encoder << hasComposition;
 
 #if PLATFORM(IOS)
+    encoder << isReplaceAllowed;
     encoder << caretRectAtStart;
     encoder << caretRectAtEnd;
     encoder << selectionRects;
@@ -89,6 +90,8 @@ bool EditorState::decode(IPC::ArgumentDecoder& decoder, EditorState& result)
         return false;
 
 #if PLATFORM(IOS)
+    if (!decoder.decode(result.isReplaceAllowed))
+        return false;
     if (!decoder.decode(result.caretRectAtStart))
         return false;
     if (!decoder.decode(result.caretRectAtEnd))
index 61b5014..d2e5c9f 100644 (file)
@@ -47,7 +47,8 @@ struct EditorState {
         , isInPlugin(false)
         , hasComposition(false)
 #if PLATFORM(IOS)
-    , selectedTextLength(0)
+        , isReplaceAllowed(false)
+        , selectedTextLength(0)
 #endif
     {
     }
@@ -63,6 +64,7 @@ struct EditorState {
     bool hasComposition;
 
 #if PLATFORM(IOS)
+    bool isReplaceAllowed;
     WebCore::IntRect caretRectAtStart;
     WebCore::IntRect caretRectAtEnd;
     Vector<WebCore::SelectionRect> selectionRects;
index b109dcb..1b12a06 100644 (file)
@@ -593,6 +593,7 @@ public:
     void getAutocorrectionContext(String& contextBefore, String& markedText, String& selectedText, String& contextAfter, uint64_t& location, uint64_t& length);
     void requestDictationContext(PassRefPtr<DictationContextCallback>);
     void replaceDictatedText(const String& oldText, const String& newText);
+    void replaceSelectedText(const String& oldText, const String& newText);
     void didReceivePositionInformation(const InteractionInformationAtPosition&);
     void getPositionInformation(const WebCore::IntPoint&, InteractionInformationAtPosition&);
     void requestPositionInformation(const WebCore::IntPoint&);
index 353c8b8..aa7c2ce 100644 (file)
@@ -133,6 +133,10 @@ static const float tapAndHoldDelay  = 0.75;
 - (void)selectWord;
 @end
 
+@interface UITextInteractionAssistant (StagingToRemove)
+- (void)scheduleReplacementsForText:(NSString *)text;
+@end
+
 @interface WKFormInputSession : NSObject <_WKFormInputSession>
 
 - (instancetype)initWithContentView:(WKContentView *)view userObject:(NSObject <NSSecureCoding> *)userObject;
@@ -851,14 +855,28 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
     // FIXME: To be implemented.
 }
 
+- (NSString *)selectedText
+{
+    return (NSString *)_page->editorState().wordAtSelection;
+}
+
+- (void)replaceText:(NSString *)text withText:(NSString *)word
+{
+    _page->replaceSelectedText(text, word);
+}
+
 - (void)_promptForReplace:(id)sender
 {
-    // FIXME: To be implemented.
+    if (_page->editorState().wordAtSelection.isEmpty())
+        return;
+
+    if ([_textSelectionAssistant respondsToSelector:@selector(scheduleReplacementsForText:)])
+        [_textSelectionAssistant scheduleReplacementsForText:_page->editorState().wordAtSelection];
 }
 
 - (void)replace:(id)sender
 {
-    // FIXME: To be implemented.
+    [[UIKeyboardImpl sharedInstance] replaceText:sender];
 }
 
 - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
@@ -908,10 +926,8 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
         return NO;
     }
 
-    if (action == @selector(_promptForReplace:)) {
-        // FIXME: need to implement
-        return NO;
-    }
+    if (action == @selector(_promptForReplace:))
+        return _page->editorState().selectionIsRange && _page->editorState().isReplaceAllowed && [[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled];
 
     if (action == @selector(select:)) {
         // Disable select in password fields so that you can't see word boundaries.
@@ -2019,6 +2035,11 @@ static UITextAutocapitalizationType toUITextAutocapitalize(WebAutocapitalizeType
         [self _updateChangedSelection];
 }
 
+- (void)selectWordForReplacement
+{
+    _page->extendSelection(WordGranularity);
+}
+
 - (void)_updateChangedSelection
 {
     if (!_selectionNeedsUpdate)
index cf3c763..dcbf1a1 100644 (file)
@@ -233,7 +233,12 @@ void WebPageProxy::replaceDictatedText(const String& oldText, const String& newT
 {
     m_process->send(Messages::WebPage::ReplaceDictatedText(oldText, newText), m_pageID);
 }
-    
+
+void WebPageProxy::replaceSelectedText(const String& oldText, const String& newText)
+{
+    m_process->send(Messages::WebPage::ReplaceSelectedText(oldText, newText), m_pageID);
+}
+
 void WebPageProxy::requestAutocorrectionData(const String& textForAutocorrection, PassRefPtr<AutocorrectionDataCallback> callback)
 {
     if (!isValid()) {
index d4dbe38..29c3a66 100644 (file)
@@ -286,7 +286,6 @@ WebPage::WebPage(uint64_t pageID, const WebPageCreationParameters& parameters)
     , m_isShowingContextMenu(false)
 #endif
 #if PLATFORM(IOS)
-    , m_shouldReturnWordAtSelection(false)
     , m_lastVisibleContentRectUpdateID(0)
     , m_hasReceivedVisibleContentRectsAfterDidCommitLoad(false)
     , m_scaleWasSetByUIProcess(false)
@@ -697,15 +696,22 @@ EditorState WebPage::editorState() const
     if (selection.isCaret()) {
         result.caretRectAtStart = view->contentsToRootView(frame.selection().absoluteCaretBounds());
         result.caretRectAtEnd = result.caretRectAtStart;
-        if (m_shouldReturnWordAtSelection)
-            result.wordAtSelection = plainText(wordRangeFromPosition(selection.start()).get());
+        // FIXME: The following check should take into account writing direction.
+        result.isReplaceAllowed = result.isContentEditable && atBoundaryOfGranularity(selection.start(), WordGranularity, DirectionForward);
+        result.wordAtSelection = plainText(wordRangeFromPosition(selection.start()).get());
     } else if (selection.isRange()) {
         result.caretRectAtStart = view->contentsToRootView(VisiblePosition(selection.start()).absoluteCaretBounds());
         result.caretRectAtEnd = view->contentsToRootView(VisiblePosition(selection.end()).absoluteCaretBounds());
         RefPtr<Range> selectedRange = selection.toNormalizedRange();
         selectedRange->collectSelectionRects(result.selectionRects);
         convertSelectionRectsToRootView(view, result.selectionRects);
-        result.selectedTextLength = plainText(selectedRange.get(), TextIteratorDefaultBehavior, true).length();
+        String selectedText = plainText(selectedRange.get(), TextIteratorDefaultBehavior, true);
+        // FIXME: We should disallow replace when the string contains only CJ characters.
+        result.isReplaceAllowed = result.isContentEditable && !result.isInPasswordField && !selectedText.containsOnlyWhitespace();
+        result.selectedTextLength = selectedText.length();
+        const int maxSelectedTextLength = 200;
+        if (selectedText.length() <= maxSelectedTextLength)
+            result.wordAtSelection = selectedText;
     }
 #endif
 
index 49afcca..4c6210f 100644 (file)
@@ -458,6 +458,7 @@ public:
     void elementDidBlur(WebCore::Node*);
     void requestDictationContext(uint64_t callbackID);
     void replaceDictatedText(const String& oldText, const String& newText);
+    void replaceSelectedText(const String& oldText, const String& newText);
     void requestAutocorrectionData(const String& textForAutocorrection, uint64_t callbackID);
     void applyAutocorrection(const String& correction, const String& originalText, uint64_t callbackID);
     void syncApplyAutocorrection(const String& correction, const String& originalText, bool& correctionApplied);
@@ -1146,7 +1147,6 @@ private:
     RefPtr<WebCore::Node> m_assistedNode;
     RefPtr<WebCore::Range> m_currentWordRange;
     RefPtr<WebCore::Node> m_interactionNode;
-    bool m_shouldReturnWordAtSelection;
     WebCore::IntPoint m_lastInteractionLocation;
 
     WebCore::ViewportConfiguration m_viewportConfiguration;
index 7739513..f9b8209 100644 (file)
@@ -56,6 +56,7 @@ messages -> WebPage LegacyReceiver {
     ExtendSelection(uint32_t granularity)
     RequestDictationContext(uint64_t callbackID)
     ReplaceDictatedText(String oldText, String newText)
+    ReplaceSelectedText(String oldText, String newText)
     RequestAutocorrectionData(String textForAutocorrection, uint64_t callbackID)
     ApplyAutocorrection(String correction, String originalText, uint64_t callbackID)
     SyncApplyAutocorrection(String correction, String originalText) -> (bool autocorrectionApplied)
index d16e04c..a25abf0 100644 (file)
@@ -582,8 +582,6 @@ void WebPage::selectWithGesture(const IntPoint& point, uint32_t granularity, uin
         }
         if (result.isNotNull())
             range = Range::create(*frame.document(), result, result);
-        if (range)
-            m_shouldReturnWordAtSelection = true;
     }
         break;
 
@@ -657,7 +655,6 @@ void WebPage::selectWithGesture(const IntPoint& point, uint32_t granularity, uin
         frame.selection().setSelectedRange(range.get(), position.affinity(), true);
 
     send(Messages::WebPageProxy::GestureCallback(point, gestureType, gestureState, static_cast<uint32_t>(flags), callbackID));
-    m_shouldReturnWordAtSelection = false;
 }
 
 static PassRefPtr<Range> rangeForPosition(Frame* frame, const VisiblePosition& position, bool baseIsStart)
@@ -1211,12 +1208,11 @@ void WebPage::selectWithTwoTouches(const WebCore::IntPoint& from, const WebCore:
 
 void WebPage::extendSelection(uint32_t granularity)
 {
+    Frame& frame = m_page->focusController().focusedOrMainFrame();
     // For the moment we handle only WordGranularity.
-    if (granularity != WordGranularity)
+    if (granularity != WordGranularity || !frame.selection().isCaret())
         return;
 
-    Frame& frame = m_page->focusController().focusedOrMainFrame();
-    ASSERT(frame.selection().isCaret());
     VisiblePosition position = frame.selection().selection().start();
     frame.selection().setSelectedRange(wordRangeFromPosition(position).get(), position.affinity(), true);
 }
@@ -1271,6 +1267,18 @@ void WebPage::requestDictationContext(uint64_t callbackID)
     send(Messages::WebPageProxy::DictationContextCallback(selectedText, contextBefore, contextAfter, callbackID));
 }
 
+void WebPage::replaceSelectedText(const String& oldText, const String& newText)
+{
+    Frame& frame = m_page->focusController().focusedOrMainFrame();
+    if (!frame.selection().isRange())
+        return;
+
+    if (plainText(frame.selection().toNormalizedRange().get()) != oldText)
+        return;
+
+    frame.editor().insertText(newText, 0);
+}
+
 void WebPage::replaceDictatedText(const String& oldText, const String& newText)
 {
     Frame& frame = m_page->focusController().focusedOrMainFrame();