[iOS] Should be able to dismiss picker or popover using the keyboard
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 3 Apr 2019 17:06:04 +0000 (17:06 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 3 Apr 2019 17:06:04 +0000 (17:06 +0000)
https://bugs.webkit.org/show_bug.cgi?id=196272
<rdar://problem/48943170>

Patch by Daniel Bates <dabates@apple.com> on 2019-04-03
Reviewed by Wenson Hsieh.

Source/WebKit:

Intercept key events and route them to the current input peripheral (if we have one). Add a base key event handler
for all form peripherals that dismisses the accessory when either the Escape key is pressed or Command + . is pressed.
I will fix this issue for the file upload picker/popover in <https://bugs.webkit.org/show_bug.cgi?id=196287>.

* SourcesCocoa.txt: Add file WKFormPeripheralBase.mm.
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView endEditingAndUpdateFocusAppearanceWithReason]): Added.
(-[WKContentView resignFirstResponderForWebView]): Write in terms of -endEditingAndUpdateFocusAppearance.
(-[WKContentView inputView]): Code style nit while I am here; add an empty line to demarcate the "crazy"
code that the FIXME is referring to and should ideally be removed from the code that is sane to always do.
(-[WKContentView accessoryDone]): When the accessory is dismissed via the Done button (iPhone) or by pressing
Escape or Command + . using a hardware keyboard (iPhone or iPad) then end the current editing session, but
do not resign first responder status as the page activation state should not be changed.
(-[WKContentView _handleKeyUIEvent:]): Bring back this code when building with USE(UIKIT_KEYBOARD_ADDITIONS)
as we need to route key events to the input peripheral (if we have one). If the input peripheral handles it
then we're done: no need to let UIKit or WebKit handle it when building with USE(UIKIT_KEYBOARD_ADDITIONS),
respectively. If the input peripheral does not handle it then do what we do now.
(-[WKContentView _elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:]):
If the element is re-focused and we have an input peripheral then we want to ensure we are first responder,
reveal the focused element, update the accessory and tell the peripheral that editing has begun (again).
For all other element re-focusing where we don't have a peripheral do what we do now. Also, update _isEditable
to reflect whether the focused element contains selectable text. This is what UIKit wants to know when it queries
-isEditable. Now that we no longer blur the focused element on iPad when the popover is dismissed and keep the
peripheral until there is a focus change we need to ensure that we give the correct answer to UIKit on view
editability. Otherwise, UIKit thinks it needs to update the text selection state when a popup button is tapped
again (as part of its gesture recognizer logic) and this causes an assertion failure in UIKit after it calls back
into us to ask for selection details, which we correctly respond with the equivalent of "we have none" and is
not the answer UIKit expects since we told it we are editable. (Currently we manage to get away with telling UIKit
we are always editable because it is not possible to perform a selection operation when we have a popover open.
Closing the popover blurs the element, setting -isEditable to NO and deallocates the peripheral avoiding this issue).
* UIProcess/ios/forms/WKFormColorControl.h:
* UIProcess/ios/forms/WKFormColorControl.mm:
(-[WKColorPopover controlEndEditing]): Dismiss the popover.
(-[WKFormColorControl initWithView:]): Modified to call base class initializer.
(-[WKFormColorControl assistantView]): Deleted.
(-[WKFormColorControl beginEditing]): Deleted.
(-[WKFormColorControl endEditing]): Deleted.
* UIProcess/ios/forms/WKFormInputControl.h:
* UIProcess/ios/forms/WKFormInputControl.mm:
(-[WKFormInputControl initWithView:]): Modified to call base class initializer.
(-[WKFormInputControl dateTimePickerCalendarType]): Write in terms of self.control.
(-[WKDateTimePopover controlEndEditing]): Dismiss the popover and tell the controller that editing ended.
(-[WKFormInputControl beginEditing]): Deleted.
(-[WKFormInputControl endEditing]): Deleted.
(-[WKFormInputControl assistantView]): Deleted.
* UIProcess/ios/forms/WKFormPeripheral.h:
* UIProcess/ios/forms/WKFormPeripheralBase.h: Added.
* UIProcess/ios/forms/WKFormPeripheralBase.mm: Added.
(-[WKFormPeripheralBase initWithView:control:]): Take ownership of the passed WKFormControl.
(-[WKFormPeripheralBase beginEditing]): Turn around and tell the control.
(-[WKFormPeripheralBase endEditing]): Ditto.
(-[WKFormPeripheralBase assistantView]): Ditto.
(-[WKFormPeripheralBase control]): Return the control.
(-[WKFormPeripheralBase handleKeyEvent:]): Dismiss the accessory (in the same way we dismiss when the Done
button is pressed on iPhone) on keydown of the Escape key or when we receive a UIKeyInputEscape event (for
Command + .).
* UIProcess/ios/forms/WKFormSelectControl.h:
* UIProcess/ios/forms/WKFormSelectControl.mm:
(-[WKFormSelectControl initWithView:]): Modified to call base class initializer.
(-[WKFormSelectControl selectRow:inComponent:extendingSelection:]): Write in terms of self.control.
(-[WKFormSelectControl selectFormPopoverTitle]): Ditto.
(-[WKFormSelectControl assistantView]): Deleted.
(-[WKFormSelectControl beginEditing]): Deleted.
(-[WKFormSelectControl endEditing]): Deleted.
* UIProcess/ios/forms/WKFormSelectPopover.mm:
(-[WKSelectPopover controlEndEditing]): Dismiss the popover.
* WebKit.xcodeproj/project.pbxproj: Add files WKFormPeripheralBase.{h, mm}.

LayoutTests:

Add test to ensure that pressing Escape or Command + . dismisses a picker.

* fast/forms/ios/dismiss-picker-using-keyboard-expected.txt: Added.
* fast/forms/ios/dismiss-picker-using-keyboard.html: Added.

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

18 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/forms/ios/dismiss-picker-using-keyboard-expected.txt [new file with mode: 0644]
LayoutTests/fast/forms/ios/dismiss-picker-using-keyboard.html [new file with mode: 0644]
Source/WebKit/ChangeLog
Source/WebKit/SourcesCocoa.txt
Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit/UIProcess/ios/forms/WKFormColorControl.h
Source/WebKit/UIProcess/ios/forms/WKFormColorControl.mm
Source/WebKit/UIProcess/ios/forms/WKFormInputControl.h
Source/WebKit/UIProcess/ios/forms/WKFormInputControl.mm
Source/WebKit/UIProcess/ios/forms/WKFormPeripheral.h
Source/WebKit/UIProcess/ios/forms/WKFormPeripheralBase.h [new file with mode: 0644]
Source/WebKit/UIProcess/ios/forms/WKFormPeripheralBase.mm [new file with mode: 0644]
Source/WebKit/UIProcess/ios/forms/WKFormSelectControl.h
Source/WebKit/UIProcess/ios/forms/WKFormSelectControl.mm
Source/WebKit/UIProcess/ios/forms/WKFormSelectPopover.mm
Source/WebKit/WebKit.xcodeproj/project.pbxproj

index 0fb6581..7b6f41a 100644 (file)
@@ -1,3 +1,16 @@
+2019-04-03  Daniel Bates  <dabates@apple.com>
+
+        [iOS] Should be able to dismiss picker or popover using the keyboard
+        https://bugs.webkit.org/show_bug.cgi?id=196272
+        <rdar://problem/48943170>
+
+        Reviewed by Wenson Hsieh.
+
+        Add test to ensure that pressing Escape or Command + . dismisses a picker.
+
+        * fast/forms/ios/dismiss-picker-using-keyboard-expected.txt: Added.
+        * fast/forms/ios/dismiss-picker-using-keyboard.html: Added.
+
 2019-04-03  Sihui Liu  <sihui_liu@apple.com>
 
         Blob type cannot be stored correctly in IDB when IDBObjectStore has autoIncrement and keyPath options
diff --git a/LayoutTests/fast/forms/ios/dismiss-picker-using-keyboard-expected.txt b/LayoutTests/fast/forms/ios/dismiss-picker-using-keyboard-expected.txt
new file mode 100644 (file)
index 0000000..ffcde27
--- /dev/null
@@ -0,0 +1,49 @@
+Tests that pressing the Escape key or Command + . dismisses a picker without causing a value change.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Dismiss by pressing escape:
+
+For color:
+PASS elementToTest.value is "#000000"
+
+For date:
+PASS elementToTest.value is "2019-03-26"
+
+For datetime-local:
+PASS elementToTest.value is "2019-03-26T14:28:05.455"
+
+For month:
+PASS elementToTest.value is "2019-03"
+
+For select:
+PASS elementToTest.value is "First"
+
+For time:
+PASS elementToTest.value is "14:28"
+
+Dismiss by pressing Command + .:
+
+For color:
+PASS elementToTest.value is "#000000"
+
+For date:
+PASS elementToTest.value is "2019-03-26"
+
+For datetime-local:
+PASS elementToTest.value is "2019-03-26T14:28:05.455"
+
+For month:
+PASS elementToTest.value is "2019-03"
+
+For select:
+PASS elementToTest.value is "First"
+
+For time:
+PASS elementToTest.value is "14:28"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/forms/ios/dismiss-picker-using-keyboard.html b/LayoutTests/fast/forms/ios/dismiss-picker-using-keyboard.html
new file mode 100644 (file)
index 0000000..23fd1d5
--- /dev/null
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../resources/js-test.js"></script>
+<script src="../../../resources/ui-helper.js"></script>
+</head>
+<body>
+<!-- Place the test container above the console output to avoid the need to compensate for page scroll. -->
+<div id="test-container">
+    <p><input type="color" class="test" data-test-name="color"></p>
+    <p><input type="date" class="test" data-test-name="date" value="2019-03-26"></p>
+    <p><input type="datetime-local" class="test" data-test-name="datetime-local" value="2019-03-26T14:28:05.455"></p>
+    <p><input type="month" class="test" data-test-name="month" value="2019-03"></p>
+    <p><select class="test" data-test-name="select">
+        <option>First</option>
+        <option>Second</option>
+    </select></p>
+    <p><input type="time" class="test" data-test-name="time" value="14:28"></p>
+</div>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+window.jsTestIsAsync = true;
+
+let elementToTest;
+
+const modifierDisplayNameMap = {
+    "altKey": "Option",
+    "ctrlKey": "Control",
+    "metaKey": "Command",
+    "shiftKey": "Shift",
+    "capsLockKey": "Caps Lock",
+}
+
+class KeyCommand {
+    constructor(key, modifiers = [])
+    {
+        this.key = key;
+        this.modifiers = modifiers;
+    }
+
+    toString()
+    {
+        let readableCommand = this.modifiers.map((modifier) => modifierDisplayNameMap[modifier]);
+        readableCommand.push(this.key);
+        return readableCommand.join(" + ");
+    }
+}
+
+function done()
+{
+    let testContainer = document.getElementById("test-container");
+    document.body.removeChild(testContainer);
+    finishJSTest()
+}
+
+async function runTest()
+{
+    if (!window.testRunner) {
+        testFailed("Must be run in WebKitTestRunner.");
+        done();
+        return;
+    }
+
+    let elementsToTest = document.getElementsByClassName("test");
+    for (let keyCommand of [new KeyCommand("escape"), new KeyCommand(".", ["metaKey"])]) {
+        debug(`<br>Dismiss by pressing ${keyCommand}:`);
+        for (elementToTest of elementsToTest) {
+            debug(`<br>For ${elementToTest.dataset.testName}:`);
+            let expectedValue = elementToTest.value;
+            await UIHelper.activateElementAndWaitForInputSession(elementToTest);
+            UIHelper.keyDown(keyCommand.key, keyCommand.modifiers);
+            await UIHelper.waitForKeyboardToHide();
+            shouldBeEqualToString("elementToTest.value", expectedValue);
+        }
+    }
+    elementToTest = null;
+    done();
+}
+
+description("Tests that pressing the Escape key or Command + . dismisses a picker without causing a value change.");
+
+runTest();
+</script>
+</body>
+</html>
index 32207f6..38a016f 100644 (file)
@@ -1,3 +1,79 @@
+2019-04-03  Daniel Bates  <dabates@apple.com>
+
+        [iOS] Should be able to dismiss picker or popover using the keyboard
+        https://bugs.webkit.org/show_bug.cgi?id=196272
+        <rdar://problem/48943170>
+
+        Reviewed by Wenson Hsieh.
+
+        Intercept key events and route them to the current input peripheral (if we have one). Add a base key event handler
+        for all form peripherals that dismisses the accessory when either the Escape key is pressed or Command + . is pressed.
+        I will fix this issue for the file upload picker/popover in <https://bugs.webkit.org/show_bug.cgi?id=196287>.
+
+        * SourcesCocoa.txt: Add file WKFormPeripheralBase.mm.
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView endEditingAndUpdateFocusAppearanceWithReason]): Added.
+        (-[WKContentView resignFirstResponderForWebView]): Write in terms of -endEditingAndUpdateFocusAppearance.
+        (-[WKContentView inputView]): Code style nit while I am here; add an empty line to demarcate the "crazy"
+        code that the FIXME is referring to and should ideally be removed from the code that is sane to always do.
+        (-[WKContentView accessoryDone]): When the accessory is dismissed via the Done button (iPhone) or by pressing
+        Escape or Command + . using a hardware keyboard (iPhone or iPad) then end the current editing session, but
+        do not resign first responder status as the page activation state should not be changed.
+        (-[WKContentView _handleKeyUIEvent:]): Bring back this code when building with USE(UIKIT_KEYBOARD_ADDITIONS)
+        as we need to route key events to the input peripheral (if we have one). If the input peripheral handles it
+        then we're done: no need to let UIKit or WebKit handle it when building with USE(UIKIT_KEYBOARD_ADDITIONS),
+        respectively. If the input peripheral does not handle it then do what we do now.
+        (-[WKContentView _elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:]):
+        If the element is re-focused and we have an input peripheral then we want to ensure we are first responder,
+        reveal the focused element, update the accessory and tell the peripheral that editing has begun (again).
+        For all other element re-focusing where we don't have a peripheral do what we do now. Also, update _isEditable
+        to reflect whether the focused element contains selectable text. This is what UIKit wants to know when it queries
+        -isEditable. Now that we no longer blur the focused element on iPad when the popover is dismissed and keep the
+        peripheral until there is a focus change we need to ensure that we give the correct answer to UIKit on view
+        editability. Otherwise, UIKit thinks it needs to update the text selection state when a popup button is tapped
+        again (as part of its gesture recognizer logic) and this causes an assertion failure in UIKit after it calls back
+        into us to ask for selection details, which we correctly respond with the equivalent of "we have none" and is
+        not the answer UIKit expects since we told it we are editable. (Currently we manage to get away with telling UIKit
+        we are always editable because it is not possible to perform a selection operation when we have a popover open.
+        Closing the popover blurs the element, setting -isEditable to NO and deallocates the peripheral avoiding this issue).
+        * UIProcess/ios/forms/WKFormColorControl.h:
+        * UIProcess/ios/forms/WKFormColorControl.mm:
+        (-[WKColorPopover controlEndEditing]): Dismiss the popover.
+        (-[WKFormColorControl initWithView:]): Modified to call base class initializer.
+        (-[WKFormColorControl assistantView]): Deleted.
+        (-[WKFormColorControl beginEditing]): Deleted.
+        (-[WKFormColorControl endEditing]): Deleted.
+        * UIProcess/ios/forms/WKFormInputControl.h:
+        * UIProcess/ios/forms/WKFormInputControl.mm:
+        (-[WKFormInputControl initWithView:]): Modified to call base class initializer.
+        (-[WKFormInputControl dateTimePickerCalendarType]): Write in terms of self.control.
+        (-[WKDateTimePopover controlEndEditing]): Dismiss the popover and tell the controller that editing ended.
+        (-[WKFormInputControl beginEditing]): Deleted.
+        (-[WKFormInputControl endEditing]): Deleted.
+        (-[WKFormInputControl assistantView]): Deleted.
+        * UIProcess/ios/forms/WKFormPeripheral.h:
+        * UIProcess/ios/forms/WKFormPeripheralBase.h: Added.
+        * UIProcess/ios/forms/WKFormPeripheralBase.mm: Added.
+        (-[WKFormPeripheralBase initWithView:control:]): Take ownership of the passed WKFormControl.
+        (-[WKFormPeripheralBase beginEditing]): Turn around and tell the control.
+        (-[WKFormPeripheralBase endEditing]): Ditto.
+        (-[WKFormPeripheralBase assistantView]): Ditto.
+        (-[WKFormPeripheralBase control]): Return the control.
+        (-[WKFormPeripheralBase handleKeyEvent:]): Dismiss the accessory (in the same way we dismiss when the Done
+        button is pressed on iPhone) on keydown of the Escape key or when we receive a UIKeyInputEscape event (for
+        Command + .).
+        * UIProcess/ios/forms/WKFormSelectControl.h:
+        * UIProcess/ios/forms/WKFormSelectControl.mm:
+        (-[WKFormSelectControl initWithView:]): Modified to call base class initializer.
+        (-[WKFormSelectControl selectRow:inComponent:extendingSelection:]): Write in terms of self.control.
+        (-[WKFormSelectControl selectFormPopoverTitle]): Ditto.
+        (-[WKFormSelectControl assistantView]): Deleted.
+        (-[WKFormSelectControl beginEditing]): Deleted.
+        (-[WKFormSelectControl endEditing]): Deleted.
+        * UIProcess/ios/forms/WKFormSelectPopover.mm:
+        (-[WKSelectPopover controlEndEditing]): Dismiss the popover.
+        * WebKit.xcodeproj/project.pbxproj: Add files WKFormPeripheralBase.{h, mm}.
+
 2019-04-03  Youenn Fablet  <youenn@apple.com>
 
         Resetting quota should take into account third party origins
index f2277fa..fd38da3 100644 (file)
@@ -367,6 +367,7 @@ UIProcess/ios/forms/WKFocusedFormControlView.mm
 UIProcess/ios/forms/WKFormColorControl.mm
 UIProcess/ios/forms/WKFormColorPicker.mm
 UIProcess/ios/forms/WKFormInputControl.mm
+UIProcess/ios/forms/WKFormPeripheralBase.mm
 UIProcess/ios/forms/WKFormPopover.mm
 UIProcess/ios/forms/WKFormSelectControl.mm
 UIProcess/ios/forms/WKFormSelectPicker.mm
index f8df13c..fc9bf6c 100644 (file)
@@ -312,7 +312,6 @@ struct WKAutoCorrectionData {
 
     BOOL _becomingFirstResponder;
     BOOL _resigningFirstResponder;
-    BOOL _dismissingAccessory;
     BOOL _needsDeferredEndScrollingSelectionUpdate;
     BOOL _isChangingFocus;
     BOOL _isBlurringFocusedElement;
index 1aa61ff..5b3c591 100644 (file)
@@ -1169,24 +1169,34 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
     return [_webView resignFirstResponder];
 }
 
-- (BOOL)resignFirstResponderForWebView
-{
-    // FIXME: Maybe we should call resignFirstResponder on the superclass
-    // and do nothing if the return value is NO.
-
-    SetForScope<BOOL> resigningFirstResponderScope { _resigningFirstResponder, YES };
+typedef NS_ENUM(NSInteger, EndEditingReason) {
+    EndEditingReasonAccessoryDone,
+    EndEditingReasonResigningFirstResponder,
+};
 
+- (void)endEditingAndUpdateFocusAppearanceWithReason:(EndEditingReason)reason
+{
     if (!_webView._retainingActiveFocusedState) {
         // We need to complete the editing operation before we blur the element.
         [self _endEditing];
-        if (_dismissingAccessory || _keyboardDidRequestDismissal)
+        if ((reason == EndEditingReasonAccessoryDone && !currentUserInterfaceIdiomIsPad()) || _keyboardDidRequestDismissal)
             _page->blurFocusedElement();
     }
 
     [self _cancelInteraction];
     [_textSelectionAssistant deactivateSelection];
-    
+
     [self _resetInputViewDeferral];
+}
+
+- (BOOL)resignFirstResponderForWebView
+{
+    // FIXME: Maybe we should call resignFirstResponder on the superclass
+    // and do nothing if the return value is NO.
+
+    SetForScope<BOOL> resigningFirstResponderScope { _resigningFirstResponder, YES };
+
+    [self endEditingAndUpdateFocusAppearanceWithReason:EndEditingReasonResigningFirstResponder];
 
     // If the user explicitly dismissed the keyboard then we will lose first responder
     // status only to gain it back again. Just don't resign in that case.
@@ -3758,8 +3768,8 @@ static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoi
 
 - (void)accessoryDone
 {
-    SetForScope<BOOL> dismissingAccessoryScope { _dismissingAccessory, YES };
-    [self resignFirstResponder];
+    [self endEditingAndUpdateFocusAppearanceWithReason:EndEditingReasonAccessoryDone];
+    _page->setIsShowingInputViewForFocusedElement(false);
 }
 
 - (void)accessoryTab:(BOOL)isNext
@@ -4362,21 +4372,28 @@ static NSString *contentTypeFromFieldName(WebCore::AutofillFieldName fieldName)
     return YES;
 }
 
-#if !USE(UIKIT_KEYBOARD_ADDITIONS)
 - (void)_handleKeyUIEvent:(::UIEvent *)event
 {
     bool isHardwareKeyboardEvent = !!event._hidEvent;
-
     // We only want to handle key event from the hardware keyboard when we are
     // first responder and we are not interacting with editable content.
-    if ([self isFirstResponder] && isHardwareKeyboardEvent && !_page->editorState().isContentEditable) {
+    if ([self isFirstResponder] && isHardwareKeyboardEvent && (_inputPeripheral || !_page->editorState().isContentEditable)) {
+        if ([_inputPeripheral respondsToSelector:@selector(handleKeyEvent:)]) {
+            if ([_inputPeripheral handleKeyEvent:event])
+                return;
+        }
+#if USE(UIKIT_KEYBOARD_ADDITIONS)
+        [super _handleKeyUIEvent:event];
+#else
         [self handleKeyEvent:event];
+#endif
         return;
     }
 
     [super _handleKeyUIEvent:event];
 }
 
+#if !USE(UIKIT_KEYBOARD_ADDITIONS)
 - (void)handleKeyEvent:(::UIEvent *)event
 {
     // WebCore has already seen the event, no need for custom processing.
@@ -4969,8 +4986,16 @@ static RetainPtr<NSObject <WKFormPeripheral>> createInputPeripheralWithView(WebK
 
     // FIXME: We should remove this check when we manage to send ElementDidFocus from the WebProcess
     // only when it is truly time to show the keyboard.
-    if (_focusedElementInformation.elementType == information.elementType && _focusedElementInformation.elementRect == information.elementRect)
+    if (_focusedElementInformation.elementType == information.elementType && _focusedElementInformation.elementRect == information.elementRect) {
+        if (_inputPeripheral) {
+            if (!self.isFirstResponder)
+                [self becomeFirstResponder];
+            [self _zoomToRevealFocusedElement];
+            [self _updateAccessory];
+            [_inputPeripheral beginEditing];
+        }
         return;
+    }
 
     [_webView _resetFocusPreservationCount];
 
@@ -4989,7 +5014,8 @@ static RetainPtr<NSObject <WKFormPeripheral>> createInputPeripheralWithView(WebK
     if (delegateImplementsWillStartInputSession)
         [inputDelegate _webView:_webView willStartInputSession:_formInputSession.get()];
 
-    BOOL editableChanged = [self setIsEditable:YES];
+    BOOL isSelectable = mayContainSelectableText(information.elementType);
+    BOOL editableChanged = [self setIsEditable:isSelectable];
     _focusedElementInformation = information;
     _traits = nil;
 
@@ -5006,7 +5032,7 @@ static RetainPtr<NSObject <WKFormPeripheral>> createInputPeripheralWithView(WebK
     [self reloadInputViews];
 #endif
 
-    if (mayContainSelectableText(_focusedElementInformation.elementType))
+    if (isSelectable)
         [self _showKeyboard];
 
     // The custom fixed position rect behavior is affected by -isFocusingElement, so if that changes we need to recompute rects.
index 43d6dcb..c3d551b 100644 (file)
 
 #if ENABLE(INPUT_TYPE_COLOR) && PLATFORM(IOS_FAMILY)
 
-#import "WKFormPeripheral.h"
+#import "WKFormPeripheralBase.h"
 
 @class WKContentView;
 
-@interface WKFormColorControl : NSObject<WKFormPeripheral>
+@interface WKFormColorControl : WKFormPeripheralBase
 - (instancetype)initWithView:(WKContentView *)view;
 @end
 
index f5539f3..57cea5f 100644 (file)
@@ -86,42 +86,23 @@ static const CGFloat colorPopoverCornerRadius = 9;
 
 - (void)controlEndEditing
 {
+    [self dismissPopoverAnimated:NO];
 }
 
 @end
 
 #pragma mark - WKFormColorControl
 
-@implementation WKFormColorControl {
-    RetainPtr<id<WKFormControl>> _control;
-}
+@implementation WKFormColorControl
 
 - (instancetype)initWithView:(WKContentView *)view
 {
-    if (!(self = [super init]))
-        return nil;
-
+    RetainPtr<NSObject <WKFormControl>> control;
     if (currentUserInterfaceIdiomIsPad())
-        _control = adoptNS([[WKColorPopover alloc] initWithView:view]);
+        control = adoptNS([[WKColorPopover alloc] initWithView:view]);
     else
-        _control = adoptNS([[WKColorPicker alloc] initWithView:view]);
-
-    return self;
-}
-
-- (UIView *)assistantView
-{
-    return [_control controlView];
-}
-
-- (void)beginEditing
-{
-    [_control controlBeginEditing];
-}
-
-- (void)endEditing
-{
-    [_control controlEndEditing];
+        control = adoptNS([[WKColorPicker alloc] initWithView:view]);
+    return [super initWithView:view control:WTFMove(control)];
 }
 
 @end
index 162c16b..e07bb49 100644 (file)
 
 #if PLATFORM(IOS_FAMILY)
 
-#import "WKFormPeripheral.h"
+#import "WKFormPeripheralBase.h"
 
 @class WKContentView;
 
-@interface WKFormInputControl : NSObject<WKFormPeripheral>
+@interface WKFormInputControl : WKFormPeripheralBase
 - (instancetype)initWithView:(WKContentView *)view;
 @end
 
index cfed2d5..f57866a 100644 (file)
@@ -239,61 +239,36 @@ static const NSTimeInterval kMillisecondsPerSecond = 1000;
 @end
 
 // WKFormInputControl
-@implementation WKFormInputControl {
-    RetainPtr<NSObject <WKFormControl>> _control;
-}
+@implementation WKFormInputControl
 
 - (instancetype)initWithView:(WKContentView *)view
 {
-    if (!(self = [super init]))
-        return nil;
-
     UIDatePickerMode mode;
 
     switch (view.focusedElementInformation.elementType) {
     case InputType::Date:
         mode = UIDatePickerModeDate;
         break;
-
     case InputType::DateTimeLocal:
         mode = UIDatePickerModeDateAndTime;
         break;
-
     case InputType::Time:
         mode = UIDatePickerModeTime;
         break;
-
     case InputType::Month:
         mode = (UIDatePickerMode)UIDatePickerModeYearAndMonth;
         break;
-
     default:
         [self release];
         return nil;
     }
 
+    RetainPtr<NSObject <WKFormControl>> control;
     if (currentUserInterfaceIdiomIsPad())
-        _control = adoptNS([[WKDateTimePopover alloc] initWithView:view datePickerMode:mode]);
+        control = adoptNS([[WKDateTimePopover alloc] initWithView:view datePickerMode:mode]);
     else
-        _control = adoptNS([[WKDateTimePicker alloc] initWithView:view datePickerMode:mode]);
-
-    return self;
-
-}
-
-- (void)beginEditing
-{
-    [_control controlBeginEditing];
-}
-
-- (void)endEditing
-{
-    [_control controlEndEditing];
-}
-
-- (UIView *)assistantView
-{
-    return [_control controlView];
+        control = adoptNS([[WKDateTimePicker alloc] initWithView:view datePickerMode:mode]);
+    return [super initWithView:view control:WTFMove(control)];
 }
 
 @end
@@ -302,12 +277,10 @@ static const NSTimeInterval kMillisecondsPerSecond = 1000;
 
 - (NSString *)dateTimePickerCalendarType
 {
-    if ([_control isKindOfClass:WKDateTimePicker.class])
-        return [(WKDateTimePicker *)_control.get() calendarType];
-
-    if ([_control isKindOfClass:WKDateTimePopover.class])
-        return [(WKDateTimePopover *)_control.get() calendarType];
-
+    if ([self.control isKindOfClass:WKDateTimePicker.class])
+        return [(WKDateTimePicker *)self.control calendarType];
+    if ([self.control isKindOfClass:WKDateTimePopover.class])
+        return [(WKDateTimePopover *)self.control calendarType];
     return nil;
 }
 
@@ -388,6 +361,8 @@ static const NSTimeInterval kMillisecondsPerSecond = 1000;
 
 - (void)controlEndEditing
 {
+    [self dismissPopoverAnimated:NO];
+    [_viewController.get().innerControl controlEndEditing];
 }
 
 - (UIView *)controlView
index 67e8873..b1812be 100644 (file)
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+@class UIEvent;
 @class UIView;
 
 @protocol WKFormPeripheral
 - (void)beginEditing;
 - (void)endEditing;
 - (UIView *)assistantView;
+@optional
+- (BOOL)handleKeyEvent:(UIEvent *)event;
 @end
 
 @protocol WKFormControl
 - (UIView *)controlView;
 - (void)controlBeginEditing;
 - (void)controlEndEditing;
+@optional
+- (BOOL)controlHandleKeyEvent:(UIEvent *)event;
 @end
diff --git a/Source/WebKit/UIProcess/ios/forms/WKFormPeripheralBase.h b/Source/WebKit/UIProcess/ios/forms/WKFormPeripheralBase.h
new file mode 100644 (file)
index 0000000..f840776
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if PLATFORM(IOS_FAMILY)
+
+#import "WKFormPeripheral.h"
+#import <wtf/Forward.h>
+
+@class WKContentView;
+
+@interface WKFormPeripheralBase : NSObject <WKFormPeripheral>
+
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithView:(WKContentView *)view control:(RetainPtr<NSObject <WKFormControl>>&&)control NS_DESIGNATED_INITIALIZER;
+
+- (void)beginEditing;
+- (void)endEditing;
+- (UIView *)assistantView;
+- (BOOL)handleKeyEvent:(UIEvent *)event;
+
+@property (nonatomic, readonly) WKContentView *view;
+@property (nonatomic, readonly) NSObject <WKFormControl> *control;
+@property (nonatomic, readonly, getter=isEditing) BOOL editing;
+
+@end
+
+#endif
diff --git a/Source/WebKit/UIProcess/ios/forms/WKFormPeripheralBase.mm b/Source/WebKit/UIProcess/ios/forms/WKFormPeripheralBase.mm
new file mode 100644 (file)
index 0000000..5f28969
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "WKFormPeripheralBase.h"
+
+#if PLATFORM(IOS_FAMILY)
+
+#import "WKContentView.h"
+#import <pal/spi/cocoa/IOKitSPI.h>
+#import <wtf/RetainPtr.h>
+
+@implementation WKFormPeripheralBase {
+    RetainPtr<NSObject <WKFormControl>> _control;
+}
+
+- (instancetype)initWithView:(WKContentView *)view control:(RetainPtr<NSObject <WKFormControl>>&&)control
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _view = view;
+    _control = WTFMove(control);
+    return self;
+}
+
+- (void)beginEditing
+{
+    _editing = YES;
+    [_control controlBeginEditing];
+}
+
+- (void)endEditing
+{
+    _editing = NO;
+    [_control controlEndEditing];
+}
+
+- (UIView *)assistantView
+{
+    return [_control controlView];
+}
+
+- (NSObject <WKFormControl> *)control
+{
+    return _control.get();
+}
+
+- (BOOL)handleKeyEvent:(UIEvent *)event
+{
+    ASSERT(event._hidEvent);
+    if ([_control respondsToSelector:@selector(controlHandleKeyEvent:)]) {
+        if ([_control controlHandleKeyEvent:event])
+            return YES;
+    }
+    if (!event._isKeyDown)
+        return NO;
+    UIPhysicalKeyboardEvent *keyEvent = (UIPhysicalKeyboardEvent *)event;
+    if (keyEvent._inputFlags & kUIKeyboardInputModifierFlagsChanged)
+        return NO;
+    if (_editing && (keyEvent._keyCode == kHIDUsage_KeyboardEscape || [keyEvent._unmodifiedInput isEqualToString:UIKeyInputEscape])) {
+        [_view accessoryDone];
+        return YES;
+    }
+    return NO;
+}
+
+@end
+
+#endif // PLATFORM(IOS_FAMILY)
index 7be651a..f695be3 100644 (file)
@@ -27,7 +27,7 @@
 
 #import "FocusedElementInformation.h"
 #import "UIKitSPI.h"
-#import "WKFormPeripheral.h"
+#import "WKFormPeripheralBase.h"
 #import "WKFormPopover.h"
 #import <UIKit/UIPickerView.h>
 
@@ -35,7 +35,7 @@ CGFloat adjustedFontSize(CGFloat textWidth, UIFont *, CGFloat initialFontSize, c
 
 @class WKContentView;
 
-@interface WKFormSelectControl : NSObject<WKFormPeripheral>
+@interface WKFormSelectControl : WKFormPeripheralBase
 - (instancetype)initWithView:(WKContentView *)view;
 @end
 
index 3b948d1..3d5c92a 100644 (file)
@@ -65,9 +65,6 @@ CGFloat adjustedFontSize(CGFloat textWidth, UIFont *font, CGFloat initialFontSiz
 
 - (instancetype)initWithView:(WKContentView *)view
 {
-    if (!(self = [super init]))
-        return nil;
-
     bool hasGroups = false;
     for (size_t i = 0; i < view.focusedElementInformation.selectOptions.size(); ++i) {
         if (view.focusedElementInformation.selectOptions[i].isGroup) {
@@ -76,29 +73,15 @@ CGFloat adjustedFontSize(CGFloat textWidth, UIFont *font, CGFloat initialFontSiz
         }
     }
 
+    RetainPtr<NSObject <WKFormControl>> control;
     if (currentUserInterfaceIdiomIsPad())
-        _control = adoptNS([[WKSelectPopover alloc] initWithView:view hasGroups:hasGroups]);
+        control = adoptNS([[WKSelectPopover alloc] initWithView:view hasGroups:hasGroups]);
     else if (view.focusedElementInformation.isMultiSelect || hasGroups)
-        _control = adoptNS([[WKMultipleSelectPicker alloc] initWithView:view]);
+        control = adoptNS([[WKMultipleSelectPicker alloc] initWithView:view]);
     else
-        _control = adoptNS([[WKSelectSinglePicker alloc] initWithView:view]);
-        
-    return self;
-}
+        control = adoptNS([[WKSelectSinglePicker alloc] initWithView:view]);
 
-- (UIView *)assistantView
-{
-    return [_control controlView];
-}
-
-- (void)beginEditing
-{
-    [_control controlBeginEditing];
-}
-
-- (void)endEditing
-{
-    [_control controlEndEditing];
+    return [super initWithView:view control:WTFMove(control)];
 }
 
 @end
@@ -107,16 +90,15 @@ CGFloat adjustedFontSize(CGFloat textWidth, UIFont *font, CGFloat initialFontSiz
 
 - (void)selectRow:(NSInteger)rowIndex inComponent:(NSInteger)componentIndex extendingSelection:(BOOL)extendingSelection
 {
-    if ([_control respondsToSelector:@selector(selectRow:inComponent:extendingSelection:)])
-        [id<WKSelectTesting>(_control.get()) selectRow:rowIndex inComponent:componentIndex extendingSelection:extendingSelection];
+    if ([self.control respondsToSelector:@selector(selectRow:inComponent:extendingSelection:)])
+        [id<WKSelectTesting>(self.control) selectRow:rowIndex inComponent:componentIndex extendingSelection:extendingSelection];
 }
 
 - (NSString *)selectFormPopoverTitle
 {
-    if (![_control isKindOfClass:[WKSelectPopover class]])
+    if (![self.control isKindOfClass:[WKSelectPopover class]])
         return nil;
-
-    return [(WKSelectPopover *)_control.get() tableViewController].title;
+    return [(WKSelectPopover *)self.control tableViewController].title;
 }
 
 @end
index e2d2bbd..5549690 100644 (file)
@@ -36,6 +36,7 @@
 #import "WebPageProxy.h"
 #import <UIKit/UIPickerView.h>
 #import <WebCore/LocalizedStrings.h>
+#import <pal/spi/cocoa/IOKitSPI.h>
 #import <wtf/RetainPtr.h>
 
 using namespace WebKit;
@@ -446,6 +447,7 @@ ALLOW_DEPRECATED_DECLARATIONS_END
 
 - (void)controlEndEditing
 {
+    [self dismissPopoverAnimated:NO];
 }
 
 - (void)_userActionDismissedPopover:(id)sender
index 1c0205e..53237ae 100644 (file)
                CE1A0BD61A48E6C60054EF74 /* TCCSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = CE1A0BD01A48E6C60054EF74 /* TCCSPI.h */; };
                CE1A0BD71A48E6C60054EF74 /* TextInputSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = CE1A0BD11A48E6C60054EF74 /* TextInputSPI.h */; };
                CE5B4C8821B73D870022E64F /* WKSyntheticFlagsChangedWebEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = CE5B4C8621B73D870022E64F /* WKSyntheticFlagsChangedWebEvent.h */; };
+               CE70EE5D22442BD000E0AF0F /* WKFormPeripheralBase.h in Headers */ = {isa = PBXBuildFile; fileRef = CE70EE5C22442BD000E0AF0F /* WKFormPeripheralBase.h */; };
                CEC8F9CB1FDF5870002635E7 /* WKWebProcessPlugInNodeHandlePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = CEC8F9CA1FDF5870002635E7 /* WKWebProcessPlugInNodeHandlePrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
                CEDA12E3152CD1B300D9E08D /* WebAlternativeTextClient.h in Headers */ = {isa = PBXBuildFile; fileRef = CEDA12DE152CCAE800D9E08D /* WebAlternativeTextClient.h */; };
                CEE4AE2B1A5DCF430002F49B /* UIKitSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = CEE4AE2A1A5DCF430002F49B /* UIKitSPI.h */; };
                CE1A0BD11A48E6C60054EF74 /* TextInputSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextInputSPI.h; sourceTree = "<group>"; };
                CE5B4C8621B73D870022E64F /* WKSyntheticFlagsChangedWebEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WKSyntheticFlagsChangedWebEvent.h; path = ios/WKSyntheticFlagsChangedWebEvent.h; sourceTree = "<group>"; };
                CE5B4C8721B73D870022E64F /* WKSyntheticFlagsChangedWebEvent.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = WKSyntheticFlagsChangedWebEvent.mm; path = ios/WKSyntheticFlagsChangedWebEvent.mm; sourceTree = "<group>"; };
+               CE70EE5A22442BB300E0AF0F /* WKFormPeripheralBase.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = WKFormPeripheralBase.mm; path = ios/forms/WKFormPeripheralBase.mm; sourceTree = "<group>"; };
+               CE70EE5C22442BD000E0AF0F /* WKFormPeripheralBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WKFormPeripheralBase.h; path = ios/forms/WKFormPeripheralBase.h; sourceTree = "<group>"; };
                CEC8F9CA1FDF5870002635E7 /* WKWebProcessPlugInNodeHandlePrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKWebProcessPlugInNodeHandlePrivate.h; sourceTree = "<group>"; };
                CEDA12DE152CCAE800D9E08D /* WebAlternativeTextClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebAlternativeTextClient.h; sourceTree = "<group>"; };
                CEDA12DF152CCAE800D9E08D /* WebAlternativeTextClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebAlternativeTextClient.cpp; sourceTree = "<group>"; };
                                C54256AF18BEC18B00DE4179 /* WKFormInputControl.h */,
                                C54256B018BEC18B00DE4179 /* WKFormInputControl.mm */,
                                C54256B118BEC18B00DE4179 /* WKFormPeripheral.h */,
+                               CE70EE5C22442BD000E0AF0F /* WKFormPeripheralBase.h */,
+                               CE70EE5A22442BB300E0AF0F /* WKFormPeripheralBase.mm */,
                                C54256B218BEC18B00DE4179 /* WKFormPopover.h */,
                                C54256B318BEC18B00DE4179 /* WKFormPopover.mm */,
                                C54256B418BEC18C00DE4179 /* WKFormSelectControl.h */,
                                E548EBD121015F0E00BE3C32 /* WKFormColorPicker.h in Headers */,
                                C54256B518BEC18C00DE4179 /* WKFormInputControl.h in Headers */,
                                C54256B718BEC18C00DE4179 /* WKFormPeripheral.h in Headers */,
+                               CE70EE5D22442BD000E0AF0F /* WKFormPeripheralBase.h in Headers */,
                                C54256B818BEC18C00DE4179 /* WKFormPopover.h in Headers */,
                                C54256BA18BEC18C00DE4179 /* WKFormSelectControl.h in Headers */,
                                0F08CF521D63C13A00B48DF1 /* WKFormSelectPicker.h in Headers */,