Changeset 249031 in webkit


Ignore:
Timestamp:
Aug 22, 2019 3:22:49 PM (5 years ago)
Author:
dbates@webkit.org
Message:

[iOS] Should show input view when became first responder if keyboard was showing when the view was resigned
https://bugs.webkit.org/show_bug.cgi?id=200902
<rdar://problem/54231756>

Reviewed by Wenson Hsieh.

Source/WebKit:

When resigning first responder save whether the peripheral host has an input view on screen,
including the software keyboard, so that we show the input view(s) again when the WKWebView
is made first responder. In Safari, this avoids the need for a person to explicitly focus an
editable element again to bring up the keyboard when returning to a tab they were previously
typing in. It also makes the behavior of switching tabs in Safari with a software keyboard
match the behavior of doing the same thing when a hardware keyboard attached.

  • UIProcess/PageClient.h:
  • UIProcess/WebPageProxy.h:
  • UIProcess/WebPageProxy.messages.in:
  • UIProcess/ios/PageClientImplIOS.h:
  • UIProcess/ios/PageClientImplIOS.mm:

(WebKit::PageClientImpl::focusedElementDidChangeInputMode):
Pass a diff of the activity state from the web process to the UI process so that we can
differentiate between an inputmode change as a result of page deactivation vs a change
caused by some other means. We need to differentiate these cases because we want to
ignore a page that sets inputmode "none" (i.e. a request to hide the keyboard) from inside
a focus event handler if the handler was called as part of the process of page activation
(i.e. switching to the tab). Google Docs is one example of a web site that sets inputmode
to "none" as a result of the page activation process.

  • UIProcess/ios/WKContentViewInteraction.h:
  • UIProcess/ios/WKContentViewInteraction.mm:

(-[WKContentView cleanupInteraction]): Clear out state.
(-[WKContentView resignFirstResponderForWebView]): Save whether the peripheral host is on screen
into a local before ending the editing session. We then copy the local into the ivar if we
actually will resign. This ordering is explicitly done because:

  1. Ending the editing session may dismiss the keyboard => we need to query the peripheral host first.
  2. If the view is being resigned as a result of a keyboard dismissal (i.e. a person pressed the hide keyboard button on iPad) then the user has indicated that they are finished with the keyboard and we do not want to show the keyboard on page re-activation => we do not want to copy the local to the ivar.
  3. If the view refuses to resign itself then it does not make sense to save the keyboard state as responder status hasn't changed.

(-[WKContentView shouldShowAutomaticKeyboardUI]): Ignore inputmode="none", if needed.
(-[WKContentView _didCommitLoadForMainFrame]): Clear out state.
(-[WKContentView isFirstResponderOrBecomingFirstResponder]): Added.
(-[WKContentView shouldShowInputViewOnPageActivation:]): Added.
(-[WKContentView _elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:]):
Update ivar if this element is being focused as a result of page activation.
(-[WKContentView _didUpdateInputMode:activityStateChanges:]): Modified to take the activity state
diff. If the input mode was changed as a result of page activation then we want to update our ivar
so that when we call -reloadInputViews and UIKit calls us back in -shouldShowAutomaticKeyboardUI we
will know to ignore inputmode set to "none" when determining whether to show the automatic keyboard UI.
Note that we do not need to check/track whether an earlier -_elementDidFocus actually started an
input session as part of updating the value of our ivar because if an input session was not started,
say the embedding client disallowed it, then we would not have a focused element => we early return from
this function. Also remove duplication and improve code readbility by making use of the convenience function
hasFocusedElement() instead of duplicating what it does.
(-[WKContentView _didUpdateInputMode:]): Deleted.

  • UIProcess/ios/WebPageProxyIOS.mm:

(WebKit::WebPageProxy::focusedElementDidChangeInputMode): Modified to take the activity state diff
and pass it through.
(WebKit::WebPageProxy::didReleaseAllTouchPoints): Pass the empty set for the activity state diff to
keep our current behavior.

  • WebProcess/WebPage/WebPage.cpp:

(WebKit::WebPage::focusedElementDidChangeInputMode): Send the activity state diff to the UI process.

LayoutTests:

Add tests to ensure that we show the keyboard when becoming first responder if the view resigned with the
keyboard on screen. Also add a test to ensure that we keep our current behavior and do NOT show the keyboard
for an autofocused text field when the view becomes first responder.

  • fast/events/ios/resources/check-keyboard-on-screen.js: Added.

(async.checkKeyboardOnScreen):
(async.checkKeyboardNotOnScreen):

  • fast/events/ios/should-not-show-keyboard-for-autofocused-field-when-becoming-first-responder-after-navigation-expected.txt: Added.
  • fast/events/ios/should-not-show-keyboard-for-autofocused-field-when-becoming-first-responder-after-navigation.html: Added.
  • fast/events/ios/show-keyboard-when-becoming-first-responder-despite-inputmode-none-expected.txt: Added.
  • fast/events/ios/show-keyboard-when-becoming-first-responder-despite-inputmode-none.html: Added.
  • fast/events/ios/show-keyboard-when-becoming-first-responder-expected.txt: Added.
  • fast/events/ios/show-keyboard-when-becoming-first-responder.html: Added.
  • resources/ui-helper.js:

(window.UIHelper.waitForKeyboardToShow.return.new.Promise): Added.
(window.UIHelper.waitForKeyboardToShow): Added.
(window.UIHelper.becomeFirstResponder): Added.

Location:
trunk
Files:
7 added
12 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r249028 r249031  
     12019-08-22  Daniel Bates  <dabates@apple.com>
     2
     3        [iOS] Should show input view when became first responder if keyboard was showing when the view was resigned
     4        https://bugs.webkit.org/show_bug.cgi?id=200902
     5        <rdar://problem/54231756>
     6
     7        Reviewed by Wenson Hsieh.
     8
     9        Add tests to ensure that we show the keyboard when becoming first responder if the view resigned with the
     10        keyboard on screen. Also add a test to ensure that we keep our current behavior and do NOT show the keyboard
     11        for an autofocused text field when the view becomes first responder.
     12
     13        * fast/events/ios/resources/check-keyboard-on-screen.js: Added.
     14        (async.checkKeyboardOnScreen):
     15        (async.checkKeyboardNotOnScreen):
     16        * fast/events/ios/should-not-show-keyboard-for-autofocused-field-when-becoming-first-responder-after-navigation-expected.txt: Added.
     17        * fast/events/ios/should-not-show-keyboard-for-autofocused-field-when-becoming-first-responder-after-navigation.html: Added.
     18        * fast/events/ios/show-keyboard-when-becoming-first-responder-despite-inputmode-none-expected.txt: Added.
     19        * fast/events/ios/show-keyboard-when-becoming-first-responder-despite-inputmode-none.html: Added.
     20        * fast/events/ios/show-keyboard-when-becoming-first-responder-expected.txt: Added.
     21        * fast/events/ios/show-keyboard-when-becoming-first-responder.html: Added.
     22        * resources/ui-helper.js:
     23        (window.UIHelper.waitForKeyboardToShow.return.new.Promise): Added.
     24        (window.UIHelper.waitForKeyboardToShow): Added.
     25        (window.UIHelper.becomeFirstResponder): Added.
     26
    1272019-08-22  Tim Horton  <timothy_horton@apple.com>
    228
  • trunk/LayoutTests/resources/ui-helper.js

    r248977 r249031  
    507507    }
    508508
     509    static waitForKeyboardToShow()
     510    {
     511        if (!this.isWebKit2() || !this.isIOSFamily())
     512            return Promise.resolve();
     513
     514        return new Promise(resolve => {
     515            testRunner.runUIScript(`
     516                (function() {
     517                    if (uiController.isShowingKeyboard)
     518                        uiController.uiScriptComplete();
     519                    else
     520                        uiController.didShowKeyboardCallback = () => uiController.uiScriptComplete();
     521                })()`, resolve);
     522        });
     523    }
     524
    509525    static getUICaretRect()
    510526    {
     
    790806
    791807        return new Promise(resolve => testRunner.runUIScript(`uiController.resignFirstResponder()`, resolve));
     808    }
     809
     810    static becomeFirstResponder()
     811    {
     812        if (!this.isWebKit2())
     813            return Promise.resolve();
     814
     815        return new Promise(resolve => testRunner.runUIScript(`uiController.becomeFirstResponder()`, resolve));
    792816    }
    793817
  • trunk/Source/WebKit/ChangeLog

    r249029 r249031  
     12019-08-22  Daniel Bates  <dabates@apple.com>
     2
     3        [iOS] Should show input view when became first responder if keyboard was showing when the view was resigned
     4        https://bugs.webkit.org/show_bug.cgi?id=200902
     5        <rdar://problem/54231756>
     6
     7        Reviewed by Wenson Hsieh.
     8
     9        When resigning first responder save whether the peripheral host has an input view on screen,
     10        including the software keyboard, so that we show the input view(s) again when the WKWebView
     11        is made first responder. In Safari, this avoids the need for a person to explicitly focus an
     12        editable element again to bring up the keyboard when returning to a tab they were previously
     13        typing in. It also makes the behavior of switching tabs in Safari with a software keyboard
     14        match the behavior of doing the same thing when a hardware keyboard attached.
     15
     16        * UIProcess/PageClient.h:
     17        * UIProcess/WebPageProxy.h:
     18        * UIProcess/WebPageProxy.messages.in:
     19        * UIProcess/ios/PageClientImplIOS.h:
     20        * UIProcess/ios/PageClientImplIOS.mm:
     21        (WebKit::PageClientImpl::focusedElementDidChangeInputMode):
     22        Pass a diff of the activity state from the web process to the UI process so that we can
     23        differentiate between an inputmode change as a result of page deactivation vs a change
     24        caused by some other means. We need to differentiate these cases because we want to
     25        ignore a page that sets inputmode "none" (i.e. a request to hide the keyboard) from inside
     26        a focus event handler if the handler was called as part of the process of page activation
     27        (i.e. switching to the tab). Google Docs is one example of a web site that sets inputmode
     28        to "none" as a result of the page activation process.
     29
     30        * UIProcess/ios/WKContentViewInteraction.h:
     31        * UIProcess/ios/WKContentViewInteraction.mm:
     32        (-[WKContentView cleanupInteraction]): Clear out state.
     33        (-[WKContentView resignFirstResponderForWebView]): Save whether the peripheral host is on screen
     34        into a local before ending the editing session. We then copy the local into the ivar if we
     35        actually will resign. This ordering is explicitly done because:
     36                1. Ending the editing session may dismiss the keyboard => we need to query the peripheral
     37                   host first.
     38                2. If the view is being resigned as a result of a keyboard dismissal (i.e. a person pressed
     39                   the hide keyboard button on iPad) then the user has indicated that they are finished
     40                   with the keyboard and we do not want to show the keyboard on page re-activation => we
     41                   do not want to copy the local to the ivar.
     42                3. If the view refuses to resign itself then it does not make sense to save the keyboard
     43                   state as responder status hasn't changed.
     44        (-[WKContentView shouldShowAutomaticKeyboardUI]): Ignore inputmode="none", if needed.
     45        (-[WKContentView _didCommitLoadForMainFrame]): Clear out state.
     46        (-[WKContentView isFirstResponderOrBecomingFirstResponder]): Added.
     47        (-[WKContentView shouldShowInputViewOnPageActivation:]): Added.
     48        (-[WKContentView _elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:]):
     49        Update ivar if this element is being focused as a result of page activation.
     50        (-[WKContentView _didUpdateInputMode:activityStateChanges:]): Modified to take the activity state
     51        diff. If the input mode was changed as a result of page activation then we want to update our ivar
     52        so that when we call -reloadInputViews and UIKit calls us back in -shouldShowAutomaticKeyboardUI we
     53        will know to ignore inputmode set to "none" when determining whether to show the automatic keyboard UI.
     54        Note that we do not need to check/track whether an earlier -_elementDidFocus actually started an
     55        input session as part of updating the value of our ivar because if an input session was not started,
     56        say the embedding client disallowed it, then we would not have a focused element => we early return from
     57        this function. Also remove duplication and improve code readbility by making use of the convenience function
     58        hasFocusedElement() instead of duplicating what it does.
     59        (-[WKContentView _didUpdateInputMode:]): Deleted.
     60        * UIProcess/ios/WebPageProxyIOS.mm:
     61        (WebKit::WebPageProxy::focusedElementDidChangeInputMode): Modified to take the activity state diff
     62        and pass it through.
     63        (WebKit::WebPageProxy::didReleaseAllTouchPoints): Pass the empty set for the activity state diff to
     64        keep our current behavior.
     65        * WebProcess/WebPage/WebPage.cpp:
     66        (WebKit::WebPage::focusedElementDidChangeInputMode): Send the activity state diff to the UI process.
    1672019-08-22  Keith Rollin  <krollin@apple.com>
    268
  • trunk/Source/WebKit/UIProcess/PageClient.h

    r249006 r249031  
    389389    virtual void updateInputContextAfterBlurringAndRefocusingElement() = 0;
    390390    virtual void elementDidBlur() = 0;
    391     virtual void focusedElementDidChangeInputMode(WebCore::InputMode) = 0;
     391    virtual void focusedElementDidChangeInputMode(WebCore::InputMode, OptionSet<WebCore::ActivityState::Flag>) = 0;
    392392    virtual void didReceiveEditorStateUpdateAfterFocus() = 0;
    393393    virtual bool isFocusingElement() = 0;
  • trunk/Source/WebKit/UIProcess/WebPageProxy.h

    r249006 r249031  
    19461946    void elementDidBlur();
    19471947    void updateInputContextAfterBlurringAndRefocusingElement();
    1948     void focusedElementDidChangeInputMode(WebCore::InputMode);
     1948    void focusedElementDidChangeInputMode(WebCore::InputMode, OptionSet<WebCore::ActivityState::Flag>);
    19491949    void didReleaseAllTouchPoints();
    19501950    void didReceiveEditorStateUpdateAfterFocus();
  • trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in

    r249006 r249031  
    410410    ElementDidBlur()
    411411    UpdateInputContextAfterBlurringAndRefocusingElement()
    412     FocusedElementDidChangeInputMode(enum:uint8_t WebCore::InputMode mode)
     412    FocusedElementDidChangeInputMode(enum:uint8_t WebCore::InputMode mode, OptionSet<WebCore::ActivityState::Flag> activityStateChanges)
    413413    ScrollingNodeScrollWillStartScroll()
    414414    ScrollingNodeScrollDidEndScroll()
  • trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.h

    r249006 r249031  
    152152    void updateInputContextAfterBlurringAndRefocusingElement() final;
    153153    void elementDidBlur() override;
    154     void focusedElementDidChangeInputMode(WebCore::InputMode) override;
     154    void focusedElementDidChangeInputMode(WebCore::InputMode, OptionSet<WebCore::ActivityState::Flag>) override;
    155155    void didReceiveEditorStateUpdateAfterFocus() override;
    156156    bool isFocusingElement() override;
  • trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm

    r249006 r249031  
    574574}
    575575
    576 void PageClientImpl::focusedElementDidChangeInputMode(WebCore::InputMode mode)
    577 {
    578     [m_contentView _didUpdateInputMode:mode];
     576void PageClientImpl::focusedElementDidChangeInputMode(WebCore::InputMode mode, OptionSet<WebCore::ActivityState::Flag> activityStateChanges)
     577{
     578    [m_contentView _didUpdateInputMode:mode activityStateChanges:activityStateChanges];
    579579}
    580580
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h

    r249006 r249031  
    338338
    339339    BOOL _keyboardDidRequestDismissal;
     340    BOOL _wasResignedWhileShowingInputView;
     341    BOOL _shouldShowAutomaticKeyboardUIWhenInputModeNone;
    340342
    341343#if USE(UIKIT_KEYBOARD_ADDITIONS)
     
    463465- (void)_elementDidBlur;
    464466- (void)_hideContextMenuHintContainer;
    465 - (void)_didUpdateInputMode:(WebCore::InputMode)mode;
     467- (void)_didUpdateInputMode:(WebCore::InputMode)mode activityStateChanges:(OptionSet<WebCore::ActivityState::Flag>)activityStateChanges;
    466468- (void)_didReceiveEditorStateUpdateAfterFocus;
    467469- (void)_hardwareKeyboardAvailabilityChanged;
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm

    r249006 r249031  
    944944    [self _resetInputViewDeferral];
    945945    _focusedElementInformation = { };
     946    _wasResignedWhileShowingInputView = NO;
    946947   
    947948    [_keyboardScrollingAnimator invalidate];
     
    12621263    SetForScope<BOOL> resigningFirstResponderScope { _resigningFirstResponder, YES };
    12631264
    1264     [self endEditingAndUpdateFocusAppearanceWithReason:EndEditingReasonResigningFirstResponder];
     1265    BOOL wasKeyboardOnScreen = UIPeripheralHost.activeInstance.isOnScreen;
     1266    [self endEditingAndUpdateFocusAppearanceWithReason:EndEditingReasonResigningFirstResponder]; // May dismiss the keyboard.
    12651267
    12661268    // If the user explicitly dismissed the keyboard then we will lose first responder
     
    12741276
    12751277    if (superDidResign) {
     1278        _wasResignedWhileShowingInputView = wasKeyboardOnScreen;
    12761279        [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture];
    12771280        _page->activityStateDidChange(WebCore::ActivityState::IsFocused);
     
    17051708    // We currently refrain from doing so because that would prevent UIKit from showing
    17061709    // the language picker when pressing the globe key to change the input language.
    1707     if (_focusedElementInformation.inputMode == WebCore::InputMode::None && !GSEventIsHardwareKeyboardAttached())
     1710    if (_focusedElementInformation.inputMode == WebCore::InputMode::None && !GSEventIsHardwareKeyboardAttached() && !_shouldShowAutomaticKeyboardUIWhenInputModeNone)
    17081711        return NO;
    17091712
     
    39763979    _seenHardwareKeyDownInNonEditableElement = NO;
    39773980#endif
     3981    _wasResignedWhileShowingInputView = NO;
    39783982    [self _elementDidBlur];
    39793983    [self _cancelLongPressGestureRecognizer];
     
    52735277}
    52745278
     5279- (BOOL)isFirstResponderOrBecomingFirstResponder
     5280{
     5281    return self.isFirstResponder || _becomingFirstResponder;
     5282}
     5283
     5284- (BOOL)shouldShowInputViewOnPageActivation:(const OptionSet<WebCore::ActivityState::Flag> &)activityStateChanges
     5285{
     5286    return [self isFirstResponderOrBecomingFirstResponder] && activityStateChanges.contains(WebCore::ActivityState::IsFocused) && _wasResignedWhileShowingInputView;
     5287}
     5288
    52755289- (void)_elementDidFocus:(const WebKit::FocusedElementInformation&)information userIsInteracting:(BOOL)userIsInteracting blurPreviousNode:(BOOL)blurPreviousNode activityStateChanges:(OptionSet<WebCore::ActivityState::Flag>)activityStateChanges userObject:(NSObject <NSSecureCoding> *)userObject
    52765290{
     
    52965310    if ([inputDelegate respondsToSelector:@selector(_webView:decidePolicyForFocusedElement:)])
    52975311        startInputSessionPolicy = [inputDelegate _webView:_webView decidePolicyForFocusedElement:focusedElementInfo.get()];
     5312
     5313    SetForScope<BOOL> shouldShowAutomaticKeyboardUIWhenInputModeNoneScope { _shouldShowAutomaticKeyboardUIWhenInputModeNone, startInputSessionPolicy == _WKFocusStartsInputSessionPolicyAuto && [self shouldShowInputViewOnPageActivation:activityStateChanges] };
    52985314
    52995315    BOOL shouldShowInputView = [&] {
     
    53055321                return YES;
    53065322
    5307             if (self.isFirstResponder || _becomingFirstResponder) {
    5308                 // When the software keyboard is being used to enter an url, only the focus activity state is changing.
    5309                 // In this case, auto focus on the page being navigated to should be disabled, unless a hardware
    5310                 // keyboard is attached.
    5311                 if (activityStateChanges && activityStateChanges != WebCore::ActivityState::IsFocused)
     5323            if ([self isFirstResponderOrBecomingFirstResponder]) {
     5324                if (_shouldShowAutomaticKeyboardUIWhenInputModeNone)
    53125325                    return YES;
    53135326
     
    55235536}
    55245537
    5525 - (void)_didUpdateInputMode:(WebCore::InputMode)mode
    5526 {
    5527     if (!self.inputDelegate || _focusedElementInformation.elementType == WebKit::InputType::None)
     5538- (void)_didUpdateInputMode:(WebCore::InputMode)mode activityStateChanges:(OptionSet<WebCore::ActivityState::Flag>)activityStateChanges
     5539{
     5540    if (!self.inputDelegate || !hasFocusedElement(_focusedElementInformation))
    55285541        return;
    55295542
    55305543#if !PLATFORM(WATCHOS)
     5544    SetForScope<BOOL> shouldShowAutomaticKeyboardUIWhenInputModeNoneScope { _shouldShowAutomaticKeyboardUIWhenInputModeNone, [self shouldShowInputViewOnPageActivation:activityStateChanges] };
    55315545    _focusedElementInformation.inputMode = mode;
    55325546    [self reloadInputViews];
  • trunk/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm

    r249006 r249031  
    933933}
    934934
    935 void WebPageProxy::focusedElementDidChangeInputMode(WebCore::InputMode mode)
     935void WebPageProxy::focusedElementDidChangeInputMode(WebCore::InputMode mode, OptionSet<WebCore::ActivityState::Flag> activityStateChanges)
    936936{
    937937#if ENABLE(TOUCH_EVENTS)
     
    942942#endif
    943943
    944     pageClient().focusedElementDidChangeInputMode(mode);
     944    pageClient().focusedElementDidChangeInputMode(mode, activityStateChanges);
    945945}
    946946
     
    950950        return;
    951951
    952     pageClient().focusedElementDidChangeInputMode(*m_pendingInputModeChange);
     952    pageClient().focusedElementDidChangeInputMode(*m_pendingInputModeChange, { });
    953953    m_pendingInputModeChange = WTF::nullopt;
    954954}
  • trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp

    r249006 r249031  
    55365536        return;
    55375537
    5538     send(Messages::WebPageProxy::FocusedElementDidChangeInputMode(mode));
     5538    send(Messages::WebPageProxy::FocusedElementDidChangeInputMode(mode, m_lastActivityStateChanges));
    55395539#else
    55405540    UNUSED_PARAM(mode);
Note: See TracChangeset for help on using the changeset viewer.