Skip to content

Commit

Permalink
[iOS 15] Unable to type in Google Docs with a hardware keyboard witho…
Browse files Browse the repository at this point in the history
…ut refocusing editor

https://bugs.webkit.org/show_bug.cgi?id=241308
rdar://90755619

Reviewed by Tim Horton.

After the changes in r277265, we no longer call into `-reloadInputViews` when starting an input
session after programmatically focusing an editable container with `inputmode="none"`, with a
hardware keyboard attached. This is because `-_shouldShowKeyboardForElement:` returns `NO` (due to
inputmode none), so we bail early at this check:

```
if (!shouldShowInputView || information.elementType == WebKit::InputType::None) {
    _page->setIsShowingInputViewForFocusedElement(false);
    return;
}
```

As a result, UIKit never resets UIKeyboardImpl's input delegate, which should now be the
`WKContentView`, and thus never consults whether we require key events (`-requiresKeyEvents`), which
in turn means that `-handleKeyWebEvent:withCompletionHandler:` is never invoked on the content view,
so we never dispatch key events to the page.

In the normal, non-editable key event case, we lazily call `-reloadInputViews` in
`-_handleKeyUIEvent:` (which is called just before keyboard WebEvents start getting dispatched by
UIKit). However, since we're still focusing an editable element in this case, we don't end up going
down this codepath.

When the hardware keyboard is not connected, avoiding the call to `-reloadInputViews` is expected,
since we aren't showing a keyboard anyways due to the fact that the element was programmatically
focused (so the user has no way of typing or dispatching key events, in the first place).

And finally, when the `inputmode` is not none, `_isFocusingElementWithKeyboard` is set to `YES`, so
we begin the input session and call `-reloadInputViews` as normal.

It's only in this `inputmode=none` case with both the hardware keyboard attached and the editable
container being programmatically focused, where we end up in a state where the user can type with a
hardware keyboard, but we haven't informed UIKit that we should receive key events.

We can fix this by consulting a separate `-_shouldShowKeyboardForElementIgnoringInputMode:` instead
which allows us to follow the normal routine for focusing an editable element with `inputmode="none"`
which includes zooming to reveal the focused element if it's on-screen and not hidden, as well as
calling the related delegate methods; the only difference is that we avoid showing the UCB or
software keyboard, by returning `YES` from `-_disableAutomaticKeyboardUI` in this case.

* LayoutTests/fast/forms/ios/keydown-in-hidden-contenteditable-with-inputmode-none-expected.txt: Added.
* LayoutTests/fast/forms/ios/keydown-in-hidden-contenteditable-with-inputmode-none.html: Added.

Add a new layout test to simulate typing in Google Docs with a hardware keyboard (i.e., focus a
hidden contenteditable container with `inputmode="none"`), and verify that we dispatch key events to
the focused editable element.

* Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView _elementTypeRequiresAccessoryView:]):
(-[WKContentView _shouldShowKeyboardForElement:]):
(-[WKContentView _shouldShowKeyboardForElementIgnoringInputMode:]):

Split this helper method into two versions (one of which ignores `inputmode=none`). See above for
more details.

(-[WKContentView _elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:]):

Canonical link: https://commits.webkit.org/251335@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@295289 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
whsieh committed Jun 6, 2022
1 parent c979970 commit 97487f0
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
This test verifies that key events are dispatched when performing a keypress in a programmatically focused hidden editable container with inputmode='none'. To manually run the test, load the page with a hardware keyboard attached and press the 'a' key.

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS Observed keydown with key: "a"
PASS Observed keypress with key: "a"
PASS Observed keyup with key: "a"
PASS observedKeyUp became true
PASS successfullyParsed is true

TEST COMPLETE

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<script src="../../../resources/js-test.js"></script>
<script src="../../../resources/ui-helper.js"></script>
<style>
div[contenteditable] {
opacity: 0;
position: absolute;
left: -1000px;
}
</style>
</head>
<body>
<div contenteditable inputmode="none"></div>
<script>
jsTestIsAsync = true;
observedKeyUp = 0;

addEventListener("load", async () => {
description("This test verifies that key events are dispatched when performing a keypress in a programmatically focused hidden editable container with inputmode='none'. To manually run the test, load the page with a hardware keyboard attached and press the 'a' key.");

const logKeyEvent = (event) => testPassed(`Observed ${event.type} with key: "${event.key}"`);
const editor = document.querySelector("div[contenteditable]");
editor.addEventListener("keydown", logKeyEvent);
editor.addEventListener("keypress", logKeyEvent);
editor.addEventListener("keyup", (event) => {
logKeyEvent(event);
observedKeyUp = true;
});

await UIHelper.setHardwareKeyboardAttached(true);

editor.focus();
await UIHelper.keyDown("a");
await shouldBecomeEqual("observedKeyUp", "true");

editor.remove();
finishJSTest();
});
</script>
</body>
</html>
18 changes: 10 additions & 8 deletions Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3465,7 +3465,7 @@ - (void)_didEndScrollingOrZooming
#endif
}

- (bool)_elementTypeRequiresAccessoryView:(WebKit::InputType)type
- (BOOL)_elementTypeRequiresAccessoryView:(WebKit::InputType)type
{
switch (type) {
case WebKit::InputType::None:
Expand All @@ -3477,7 +3477,7 @@ - (bool)_elementTypeRequiresAccessoryView:(WebKit::InputType)type
case WebKit::InputType::DateTimeLocal:
case WebKit::InputType::Month:
case WebKit::InputType::Time:
return false;
return NO;
case WebKit::InputType::Select: {
#if ENABLE(IOS_FORM_CONTROL_REFRESH)
if (self._shouldUseContextMenusForFormControls)
Expand Down Expand Up @@ -6650,15 +6650,17 @@ static bool mayContainSelectableText(WebKit::InputType type)
}
}

- (bool)_shouldShowKeyboardForElement:(const WebKit::FocusedElementInformation&)information
- (BOOL)_shouldShowKeyboardForElement:(const WebKit::FocusedElementInformation&)information
{
if (information.inputMode == WebCore::InputMode::None)
return false;
return NO;

if (mayContainSelectableText(information.elementType))
return true;
return [self _shouldShowKeyboardForElementIgnoringInputMode:information];
}

return [self _elementTypeRequiresAccessoryView:information.elementType];
- (BOOL)_shouldShowKeyboardForElementIgnoringInputMode:(const WebKit::FocusedElementInformation&)information
{
return mayContainSelectableText(information.elementType) || [self _elementTypeRequiresAccessoryView:information.elementType];
}

static RetainPtr<NSObject <WKFormPeripheral>> createInputPeripheralWithView(WebKit::InputType type, WKContentView *view)
Expand Down Expand Up @@ -6738,7 +6740,7 @@ - (void)_elementDidFocus:(const WebKit::FocusedElementInformation&)information u
if (_isChangingFocus)
return YES;

if (_isFocusingElementWithKeyboard && [UIKeyboard isInHardwareKeyboardMode])
if ([self _shouldShowKeyboardForElementIgnoringInputMode:information] && UIKeyboard.isInHardwareKeyboardMode)
return YES;
#endif
}
Expand Down

0 comments on commit 97487f0

Please sign in to comment.