[iOS] Can't select text after dismissing the keyboard when changing focus
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 15 Oct 2018 19:47:03 +0000 (19:47 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 15 Oct 2018 19:47:03 +0000 (19:47 +0000)
commit09d9d4c52f5b77aa8d7d48995628d1fc530de25c
tree9767b9671454a412317c013f63fb0d46aede702b
parentc321e1be9e08e3bcfbac1387b17d9a84eb5cb82b
[iOS] Can't select text after dismissing the keyboard when changing focus
https://bugs.webkit.org/show_bug.cgi?id=190563
<rdar://problem/44613559>

Reviewed by Tim Horton.

Source/WebKit:

In r230686, we switched from using UIWKSelectionAssistant to UIWKTextInteractionAssistant for handling selection
in non-editable content on iOS; as such, when an editable element loses focus, instead of switching from the
text interaction assistant to the web selection assistant as we've previously done, we now reset our text
interaction assistant by calling `-[UIWKTextInteractionAssistant setGestureRecognizers]`, which removes all of
the current text selection gesture recognizers from WKContentView and regenerates them by building up a tree of
`UITextInteraction`s and adding them to the assistant (see `-[UITextInteractionAssistant
addGestureRecognizersToView:]`). In particular, `_UITextSelectionForceGesture` is the gesture recognizer used to
trigger text selection when long pressing.

After dismissing the keyboard by tapping the "Done" button, the UITextInteractions and gesture recognizers on
the interaction assistant include:

    <UITextInteraction>
        …
        <UITextIndirectNonEditableInteraction>
            <_UIKeyboardBasedNonEditableTextSelectionInteraction>
              ↳ "_UIKeyboardTextSelectionGestureForcePress" → <_UITextSelectionForceGesture>

However, after the keyboard dismisses due to an editable element losing focus, the UITextInteractions on the
interaction assistant look like this:

    <UITextInteraction>
        …
        <UITextIndirectNonEditableInteraction>

Subsequently, the lack of a `_UIKeyboardBasedNonEditableTextSelectionInteraction` makes text selection by long
pressing impossible, since the `_UITextSelectionForceGesture` is never introduced to `WKContentView`. In UIKit,
`UITextIndirectNonEditableInteraction` only adds `_UIKeyboardBasedNonEditableTextSelectionInteraction` as a
child if the text input view — in our case, WKContentView — is missing an input delegate (see `-initWithView:`).
In the case where the Done button is used to dismiss the keyboard, WKContentView loses first responder, and the
input delegate of WKContentView is cleared out early on, before we call `-stopAssistingKeyboard`:

    -[WKContentView(WKInteraction) setInputDelegate:]
    -[UIKeyboardImpl setDelegate:force:]
    -[UIPeripheralHost(UIKitInternal) _reloadInputViewsForResponder:]
    -[UIResponder _finishResignFirstResponder]
    -[UIResponder resignFirstResponder]
    -[WKContentView(WKInteraction) resignFirstResponderForWebView]
    -[UIKeyboardImpl dismissKeyboard]

However, in the case where the focused element is blurred, we end up clearing out the delegate in
`-_stopAssistingNode`, *after* we've already called `-setGestureRecognizers` on the interaction assistant. This
means UIKit will skip adding `_UIKeyboardBasedNonEditableTextSelectionInteraction` to the text interaction
assistant.

    -[WKContentView(WKInteraction) setInputDelegate:]
    -[UIKeyboardImpl setDelegate:force:]
    -[UIPeripheralHost(UIKitInternal) _reloadInputViewsForResponder:]
    -[UIResponder(UIResponderInputViewAdditions) reloadInputViews]
    -[WKContentView(WKInteraction) _stopAssistingNode]

To fix this, we simply reset our `inputDelegate` earlier in `_stopAssistingKeyboard` instead of waiting until
we reload input views. This ensures that UIKit sets up the text interaction assistant's gestures when changing
focus in the same way as we would when the keyboard is dismissed via `-resignFirstResponder` (e.g. when pressing
the Done button).

Test: editing/selection/ios/select-text-after-changing-focus.html

* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView setupInteraction]):
(-[WKContentView setUpTextSelectionAssistant]):
(-[WKContentView _startAssistingKeyboard]):
(-[WKContentView _stopAssistingKeyboard]):
(-[WKContentView useSelectionAssistantWithGranularity:]): Deleted.

Additionally rename this to -setUpTextSelectionAssistant and remove the selection granularity argument. This was
previously used to switch between web and text interaction assistants.

Tools:

* DumpRenderTree/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::isShowingKeyboard const):

Add a new UIScriptController method that returns whether the keyboard is shown. See `ui-helper.js` for more
details.

* TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
* TestRunnerShared/UIScriptContext/UIScriptController.cpp:
(WTR::UIScriptController::isShowingKeyboard const):
* TestRunnerShared/UIScriptContext/UIScriptController.h:
* WebKitTestRunner/cocoa/TestRunnerWKWebView.h:
* WebKitTestRunner/cocoa/TestRunnerWKWebView.mm:

Also rename the `isShowingKeyboard` Objective-C property to the more canonical `showingKeyboard`, with
`isShowingKeyboard` as the getter method.

(-[TestRunnerWKWebView _invokeShowKeyboardCallbackIfNecessary]):
(-[TestRunnerWKWebView _invokeHideKeyboardCallbackIfNecessary]):
* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::isShowingKeyboard const):

LayoutTests:

Add a new layout test to check that the user can make a selection by long pressing after the keyboard is
dismissed due to changing the focused element.

* editing/selection/ios/select-text-after-changing-focus-expected.txt: Added.
* editing/selection/ios/select-text-after-changing-focus.html: Added.
* resources/ui-helper.js:

Also tweak the behavior of `UIHelper.waitForKeyboardToHide()`, so that it resolves immediately if the keyboard
is not shown. This allows us to ensure that tests which use `UIHelper.waitForKeyboardToHide()` are robust in the
case where they wait for another action to complete (e.g. a simulated tap) prior to registering a keyboard
hiding callback.

(window.UIHelper.waitForKeyboardToHide.return.new.Promise):
(window.UIHelper.waitForKeyboardToHide):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@237135 268f45cc-cd09-0410-ab3c-d52691b4dbfc
14 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/selection/ios/select-text-after-changing-focus-expected.txt [new file with mode: 0644]
LayoutTests/editing/selection/ios/select-text-after-changing-focus.html [new file with mode: 0644]
LayoutTests/resources/ui-helper.js
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Tools/ChangeLog
Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm
Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl
Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp
Tools/TestRunnerShared/UIScriptContext/UIScriptController.h
Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.h
Tools/WebKitTestRunner/cocoa/TestRunnerWKWebView.mm
Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm