[iOS] Provide a way to suppress software keyboards on WKWebView's WKContentView
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 6 Feb 2020 06:44:38 +0000 (06:44 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 6 Feb 2020 06:44:38 +0000 (06:44 +0000)
https://bugs.webkit.org/show_bug.cgi?id=207272
<rdar://problem/59190828>

Reviewed by Tim Horton.

Source/WebKit:

Allow SPI clients to suppress the software keyboard using -_setSuppressSoftwareKeyboard: on WKWebView by
propagating the call to the WKContentView, which is the view that actually becomes first responder when an
element (e.g. a form control) is focused.

Test: KeyboardInputTests.SuppressSoftwareKeyboard

* Platform/spi/ios/UIKitSPI.h:
* UIProcess/API/ios/WKWebViewIOS.mm:
(-[WKWebView _setSuppressSoftwareKeyboard:]):

Tools:

Add an API test to verify (through UIKeyboardImpl) that the software keyboard should be suppressed when setting
-_setSuppressSoftwareKeyboard: on the web view. This involves making `-makeKeyWindow` "work" in the API testing
environment on iOS, by swizzling out both `-[NSBundle bundleIdentifier]` and `-[UIApplication keyWindow]`.

* TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm:
(TestWebKitAPI::overrideBundleIdentifier):
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(setOverriddenApplicationKeyWindow):
(applicationKeyWindowOverride):
(-[TestWKWebViewHostWindow makeKeyWindow]):
* TestWebKitAPI/ios/DragAndDropSimulatorIOS.mm:
* TestWebKitAPI/ios/UIKitSPI.h:

Also move the `UIResponder (UIKitSPI)` category from the internal section to the SPI section; this is redundant
when using the internal SDK to build, since these methods are already declared in UIResponder_Private.h.

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

Source/WebKit/ChangeLog
Source/WebKit/Platform/spi/ios/UIKitSPI.h
Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm
Tools/TestWebKitAPI/cocoa/TestWKWebView.mm
Tools/TestWebKitAPI/ios/DragAndDropSimulatorIOS.mm
Tools/TestWebKitAPI/ios/UIKitSPI.h

index 90d78cc..973219e 100644 (file)
@@ -1,3 +1,21 @@
+2020-02-05  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] Provide a way to suppress software keyboards on WKWebView's WKContentView
+        https://bugs.webkit.org/show_bug.cgi?id=207272
+        <rdar://problem/59190828>
+
+        Reviewed by Tim Horton.
+
+        Allow SPI clients to suppress the software keyboard using -_setSuppressSoftwareKeyboard: on WKWebView by
+        propagating the call to the WKContentView, which is the view that actually becomes first responder when an
+        element (e.g. a form control) is focused.
+
+        Test: KeyboardInputTests.SuppressSoftwareKeyboard
+
+        * Platform/spi/ios/UIKitSPI.h:
+        * UIProcess/API/ios/WKWebViewIOS.mm:
+        (-[WKWebView _setSuppressSoftwareKeyboard:]):
+
 2020-02-05  Don Olmstead  <don.olmstead@sony.com>
 
         [PlayStation] Miscellaneous build fixes February 2020 edition
index 6854363..b30670d 100644 (file)
@@ -1237,6 +1237,7 @@ typedef NS_OPTIONS(NSInteger, UIWKDocumentRequestFlags) {
 - (UIResponder *)firstResponder;
 - (void)pasteAndMatchStyle:(id)sender;
 - (void)makeTextWritingDirectionNatural:(id)sender;
+@property (nonatomic, setter=_setSuppressSoftwareKeyboard:) BOOL _suppressSoftwareKeyboard;
 @end
 
 @interface _UINavigationInteractiveTransitionBase ()
index c813507..8c9928a 100644 (file)
@@ -2851,6 +2851,12 @@ static WebCore::UserInterfaceLayoutDirection toUserInterfaceLayoutDirection(UISe
     }
 }
 
+- (void)_setSuppressSoftwareKeyboard:(BOOL)suppressSoftwareKeyboard
+{
+    [super _setSuppressSoftwareKeyboard:suppressSoftwareKeyboard];
+    [_contentView _setSuppressSoftwareKeyboard:suppressSoftwareKeyboard];
+}
+
 - (void)_snapshotRect:(CGRect)rectInViewCoordinates intoImageOfWidth:(CGFloat)imageWidth completionHandler:(void(^)(CGImageRef))completionHandler
 {
     if (_dynamicViewportUpdateMode != WebKit::DynamicViewportUpdateMode::NotResizing) {
index 5e6c726..403c5c2 100644 (file)
@@ -1,3 +1,27 @@
+2020-02-05  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] Provide a way to suppress software keyboards on WKWebView's WKContentView
+        https://bugs.webkit.org/show_bug.cgi?id=207272
+        <rdar://problem/59190828>
+
+        Reviewed by Tim Horton.
+
+        Add an API test to verify (through UIKeyboardImpl) that the software keyboard should be suppressed when setting
+        -_setSuppressSoftwareKeyboard: on the web view. This involves making `-makeKeyWindow` "work" in the API testing
+        environment on iOS, by swizzling out both `-[NSBundle bundleIdentifier]` and `-[UIApplication keyWindow]`.
+
+        * TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm:
+        (TestWebKitAPI::overrideBundleIdentifier):
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (setOverriddenApplicationKeyWindow):
+        (applicationKeyWindowOverride):
+        (-[TestWKWebViewHostWindow makeKeyWindow]):
+        * TestWebKitAPI/ios/DragAndDropSimulatorIOS.mm:
+        * TestWebKitAPI/ios/UIKitSPI.h:
+
+        Also move the `UIResponder (UIKitSPI)` category from the internal section to the SPI section; this is redundant
+        when using the internal SDK to build, since these methods are already declared in UIResponder_Private.h.
+
 2020-02-05  Don Olmstead  <don.olmstead@sony.com>
 
         [PlayStation] Build a shared JavaScriptCore
index 20d2aa1..3f0661d 100644 (file)
@@ -28,6 +28,7 @@
 #if PLATFORM(IOS_FAMILY)
 
 #import "IPadUserInterfaceSwizzler.h"
+#import "InstanceMethodSwizzler.h"
 #import "PlatformUtilities.h"
 #import "TestInputDelegate.h"
 #import "TestWKWebView.h"
@@ -629,6 +630,32 @@ TEST(KeyboardInputTests, SupportsImagePaste)
     EXPECT_TRUE(contentView.supportsImagePaste);
 }
 
+static NSString *overrideBundleIdentifier()
+{
+    return @"com.apple.TestWebKitAPI";
+}
+
+TEST(KeyboardInputTests, SuppressSoftwareKeyboard)
+{
+    InstanceMethodSwizzler bundleIdentifierSwizzler(NSBundle.class, @selector(bundleIdentifier), reinterpret_cast<IMP>(overrideBundleIdentifier));
+    UIApplicationInitialize();
+    UIApplicationInstantiateSingleton(UIApplication.class);
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView _setSuppressSoftwareKeyboard:YES];
+    [[webView window] makeKeyWindow];
+
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[&](WKWebView *, id <_WKFocusedElementInfo>) {
+        return _WKFocusStartsInputSessionPolicyAllow;
+    }];
+
+    [webView _setInputDelegate:inputDelegate.get()];
+    [webView synchronouslyLoadHTMLString:@"<body contenteditable>"];
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+    EXPECT_TRUE(UIKeyboardImpl.sharedInstance._shouldSuppressSoftwareKeyboard);
+}
+
 } // namespace TestWebKitAPI
 
 #endif // PLATFORM(IOS_FAMILY)
index 439b9cc..d0e515f 100644 (file)
@@ -27,6 +27,7 @@
 #import "TestWKWebView.h"
 
 #import "ClassMethodSwizzler.h"
+#import "InstanceMethodSwizzler.h"
 #import "TestNavigationDelegate.h"
 #import "Utilities.h"
 
@@ -306,6 +307,30 @@ NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
     return _forceKeyWindow || [super isKeyWindow];
 }
 
+#if PLATFORM(IOS_FAMILY)
+static NeverDestroyed<RetainPtr<UIWindow>> gOverriddenApplicationKeyWindow;
+static NeverDestroyed<std::unique_ptr<InstanceMethodSwizzler>> gApplicationKeyWindowSwizzler;
+static NeverDestroyed<std::unique_ptr<InstanceMethodSwizzler>> gSharedApplicationSwizzler;
+
+static void setOverriddenApplicationKeyWindow(UIWindow *window)
+{
+    if (gOverriddenApplicationKeyWindow.get() == window)
+        return;
+
+    ASSERT(UIApplication.sharedApplication);
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        gApplicationKeyWindowSwizzler.get() = makeUnique<InstanceMethodSwizzler>(UIApplication.class, @selector(keyWindow), reinterpret_cast<IMP>(applicationKeyWindowOverride));
+    });
+    gOverriddenApplicationKeyWindow.get() = window;
+}
+
+static UIWindow *applicationKeyWindowOverride(id, SEL)
+{
+    return gOverriddenApplicationKeyWindow.get().get();
+}
+#endif
+
 - (void)makeKeyWindow
 {
     if (_forceKeyWindow)
@@ -315,6 +340,7 @@ NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
 #if PLATFORM(MAC)
     [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidBecomeKeyNotification object:self];
 #else
+    setOverriddenApplicationKeyWindow(self);
     [[NSNotificationCenter defaultCenter] postNotificationName:UIWindowDidBecomeKeyNotification object:self];
 #endif
 }
index e11f2d1..ab118f2 100644 (file)
@@ -41,9 +41,6 @@
 #import <wtf/RetainPtr.h>
 #import <wtf/SoftLinking.h>
 
-SOFT_LINK_FRAMEWORK(UIKit)
-SOFT_LINK(UIKit, UIApplicationInstantiateSingleton, void, (Class singletonClass), (singletonClass))
-
 using namespace TestWebKitAPI;
 
 @implementation WKWebView (DragAndDropTesting)
index 81244d9..0e20f76 100644 (file)
@@ -62,6 +62,7 @@ IGNORE_WARNINGS_END
 WTF_EXTERN_C_BEGIN
 
 void UIApplicationInitialize(void);
+void UIApplicationInstantiateSingleton(Class principalClass);
 
 WTF_EXTERN_C_END
 
@@ -195,6 +196,16 @@ IGNORE_WARNINGS_END
 @property (nonatomic, readonly) CGRect _referenceBounds;
 @end
 
+@interface UIResponder (UIKitSPI)
+- (UIResponder *)firstResponder;
+- (void)makeTextWritingDirectionNatural:(id)sender;
+@property (nonatomic, setter=_setSuppressSoftwareKeyboard:) BOOL _suppressSoftwareKeyboard;
+@end
+
+@interface UIKeyboardImpl : UIView
++ (instancetype)sharedInstance;
+@end
+
 #endif // USE(APPLE_INTERNAL_SDK)
 
 #define UIWKDocumentRequestMarkedTextRects (1 << 5)
@@ -207,15 +218,14 @@ IGNORE_WARNINGS_END
 @property (nonatomic, copy, setter=_setTitle:) NSString *_title;
 @end
 
-@interface UIResponder (UIKitSPI)
-- (UIResponder *)firstResponder;
-- (void)makeTextWritingDirectionNatural:(id)sender;
-@end
-
 @interface UIKeyboard ()
 + (BOOL)isInHardwareKeyboardMode;
 @end
 
+@interface UIKeyboardImpl (UIKitIPI)
+- (BOOL)_shouldSuppressSoftwareKeyboard;
+@end
+
 #if PLATFORM(IOS)
 
 @protocol UIDropInteractionDelegate_Private <UIDropInteractionDelegate>