Source/WebKit:
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / ios / KeyboardInputTestsIOS.mm
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27
28 #if PLATFORM(IOS_FAMILY)
29
30 #import "IPadUserInterfaceSwizzler.h"
31 #import "PlatformUtilities.h"
32 #import "TestInputDelegate.h"
33 #import "TestWKWebView.h"
34 #import "UIKitSPI.h"
35 #import <WebKit/WKWebViewPrivate.h>
36 #import <WebKitLegacy/WebEvent.h>
37 #import <cmath>
38
39 @interface InputAssistantItemTestingWebView : TestWKWebView
40 + (UIBarButtonItemGroup *)leadingItemsForWebView:(WKWebView *)webView;
41 + (UIBarButtonItemGroup *)trailingItemsForWebView:(WKWebView *)webView;
42 @end
43
44 @implementation InputAssistantItemTestingWebView {
45     RetainPtr<UIBarButtonItemGroup> _leadingItems;
46     RetainPtr<UIBarButtonItemGroup> _trailingItems;
47 }
48
49 - (void)fakeLeadingBarButtonItemAction
50 {
51 }
52
53 - (void)fakeTrailingBarButtonItemAction
54 {
55 }
56
57 + (UIImage *)barButtonIcon
58 {
59     return [UIImage imageNamed:@"TestWebKitAPI.resources/icon.png"];
60 }
61
62 + (UIBarButtonItemGroup *)leadingItemsForWebView:(WKWebView *)webView
63 {
64     static dispatch_once_t onceToken;
65     static UIBarButtonItemGroup *sharedItems;
66     dispatch_once(&onceToken, ^{
67         auto leadingItem = adoptNS([[UIBarButtonItem alloc] initWithImage:self.barButtonIcon style:UIBarButtonItemStylePlain target:webView action:@selector(fakeLeadingBarButtonItemAction)]);
68         sharedItems = [[UIBarButtonItemGroup alloc] initWithBarButtonItems:@[ leadingItem.get() ] representativeItem:nil];
69     });
70     return sharedItems;
71 }
72
73 + (UIBarButtonItemGroup *)trailingItemsForWebView:(WKWebView *)webView
74 {
75     static dispatch_once_t onceToken;
76     static UIBarButtonItemGroup *sharedItems;
77     dispatch_once(&onceToken, ^{
78         auto trailingItem = adoptNS([[UIBarButtonItem alloc] initWithImage:self.barButtonIcon style:UIBarButtonItemStylePlain target:webView action:@selector(fakeTrailingBarButtonItemAction)]);
79         sharedItems = [[UIBarButtonItemGroup alloc] initWithBarButtonItems:@[ trailingItem.get() ] representativeItem:nil];
80     });
81     return sharedItems;
82 }
83
84 - (UITextInputAssistantItem *)inputAssistantItem
85 {
86     auto assistantItem = adoptNS([[UITextInputAssistantItem alloc] init]);
87     [assistantItem setLeadingBarButtonGroups:@[[InputAssistantItemTestingWebView leadingItemsForWebView:self]]];
88     [assistantItem setTrailingBarButtonGroups:@[[InputAssistantItemTestingWebView trailingItemsForWebView:self]]];
89     return assistantItem.autorelease();
90 }
91
92 @end
93
94 @implementation TestWKWebView (KeyboardInputTests)
95
96 static CGRect rounded(CGRect rect)
97 {
98     return CGRectMake(std::round(rect.origin.x), std::round(rect.origin.y), std::round(rect.size.width), std::round(rect.size.height));
99 }
100
101 - (void)waitForCaretViewFrameToBecome:(CGRect)frame
102 {
103     BOOL hasEmittedWarning = NO;
104     NSTimeInterval secondsToWaitUntilWarning = 2;
105     NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
106     while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
107         CGRect currentFrame = rounded(self.caretViewRectInContentCoordinates);
108         if (CGRectEqualToRect(currentFrame, frame))
109             break;
110
111         if (hasEmittedWarning || startTime + secondsToWaitUntilWarning >= [NSDate timeIntervalSinceReferenceDate])
112             continue;
113
114         NSLog(@"Expected a caret rect of %@, but still observed %@", NSStringFromCGRect(frame), NSStringFromCGRect(currentFrame));
115         hasEmittedWarning = YES;
116     }
117 }
118
119 - (void)waitForSelectionViewRectsToBecome:(NSArray<NSValue *> *)selectionRects
120 {
121     BOOL hasEmittedWarning = NO;
122     NSTimeInterval secondsToWaitUntilWarning = 2;
123     NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
124     while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
125         NSArray<NSValue *> *currentRects = self.selectionViewRectsInContentCoordinates;
126         BOOL selectionRectsMatch = YES;
127         if (currentRects.count == selectionRects.count) {
128             for (NSUInteger index = 0; index < selectionRects.count; ++index)
129                 selectionRectsMatch |= CGRectEqualToRect(selectionRects[index].CGRectValue, rounded(currentRects[index].CGRectValue));
130         } else
131             selectionRectsMatch = NO;
132
133         if (selectionRectsMatch)
134             break;
135
136         if (hasEmittedWarning || startTime + secondsToWaitUntilWarning >= [NSDate timeIntervalSinceReferenceDate])
137             continue;
138
139         NSLog(@"Expected a selection rects of %@, but still observed %@", selectionRects, currentRects);
140         hasEmittedWarning = YES;
141     }
142 }
143
144 - (UIBarButtonItemGroup *)lastTrailingBarButtonGroup
145 {
146     return self.firstResponder.inputAssistantItem.trailingBarButtonGroups.lastObject;
147 }
148
149 @end
150
151 @interface CustomInputWebView : TestWKWebView
152 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration inputView:(UIView *)inputView inputAccessoryView:(UIView *)inputAccessoryView;
153 @end
154
155 @implementation CustomInputWebView {
156     RetainPtr<UIView> _customInputView;
157     RetainPtr<UIView> _customInputAccessoryView;
158 }
159
160 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration inputView:(UIView *)inputView inputAccessoryView:(UIView *)inputAccessoryView
161 {
162     if (self = [super initWithFrame:frame configuration:configuration]) {
163         _customInputView = inputView;
164         _customInputAccessoryView = inputAccessoryView;
165     }
166     return self;
167 }
168
169 - (UIView *)inputView
170 {
171     return _customInputView.get();
172 }
173
174 - (UIView *)inputAccessoryView
175 {
176     return _customInputAccessoryView.get();
177 }
178
179 @end
180
181 static RetainPtr<TestWKWebView> webViewWithAutofocusedInput(const RetainPtr<TestInputDelegate>& inputDelegate)
182 {
183     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
184
185     bool doneWaiting = false;
186     [inputDelegate setFocusStartsInputSessionPolicyHandler:[&] (WKWebView *, id <_WKFocusedElementInfo>) -> _WKFocusStartsInputSessionPolicy {
187         doneWaiting = true;
188         return _WKFocusStartsInputSessionPolicyAllow;
189     }];
190     [webView _setInputDelegate:inputDelegate.get()];
191     [webView synchronouslyLoadHTMLString:@"<meta name='viewport' content='width=device-width, initial-scale=1'><input autofocus>"];
192
193     TestWebKitAPI::Util::run(&doneWaiting);
194     doneWaiting = false;
195     return webView;
196 }
197
198 static RetainPtr<TestWKWebView> webViewWithAutofocusedInput()
199 {
200     auto inputDelegate = adoptNS([TestInputDelegate new]);
201     return webViewWithAutofocusedInput(inputDelegate);
202 }
203
204 namespace TestWebKitAPI {
205
206 TEST(KeyboardInputTests, FormNavigationAssistantBarButtonItems)
207 {
208     IPadUserInterfaceSwizzler iPadUserInterface;
209
210     auto inputDelegate = adoptNS([TestInputDelegate new]);
211     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
212     [webView _setInputDelegate:inputDelegate.get()];
213     [inputDelegate setFocusStartsInputSessionPolicyHandler:[&] (WKWebView *, id <_WKFocusedElementInfo>) -> _WKFocusStartsInputSessionPolicy {
214         return _WKFocusStartsInputSessionPolicyAllow;
215     }];
216     [webView synchronouslyLoadHTMLString:@"<body contenteditable>"];
217     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
218
219     EXPECT_EQ(2U, [webView lastTrailingBarButtonGroup].barButtonItems.count);
220     EXPECT_FALSE([webView lastTrailingBarButtonGroup].hidden);
221
222     if (![UIWebFormAccessory instancesRespondToSelector:@selector(setNextPreviousItemsVisible:)]) {
223         // The rest of this test requires UIWebFormAccessory to be able to show or hide its next and previous items.
224         return;
225     }
226
227     [webView _setEditable:YES];
228     EXPECT_TRUE([webView lastTrailingBarButtonGroup].hidden);
229
230     [webView _setEditable:NO];
231     EXPECT_FALSE([webView lastTrailingBarButtonGroup].hidden);
232 }
233
234 TEST(KeyboardInputTests, ModifyInputAssistantItemBarButtonGroups)
235 {
236     auto inputDelegate = adoptNS([TestInputDelegate new]);
237     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
238     [webView _setInputDelegate:inputDelegate.get()];
239     [webView synchronouslyLoadHTMLString:@"<body contenteditable>"];
240     UITextInputAssistantItem *item = [webView inputAssistantItem];
241     UIBarButtonItemGroup *leadingItems = [InputAssistantItemTestingWebView leadingItemsForWebView:webView.get()];
242     UIBarButtonItemGroup *trailingItems = [InputAssistantItemTestingWebView trailingItemsForWebView:webView.get()];
243     item.leadingBarButtonGroups = @[ leadingItems ];
244     item.trailingBarButtonGroups = @[ trailingItems ];
245
246     [inputDelegate setFocusStartsInputSessionPolicyHandler:[&] (WKWebView *, id <_WKFocusedElementInfo>) -> _WKFocusStartsInputSessionPolicy {
247         return _WKFocusStartsInputSessionPolicyAllow;
248     }];
249     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
250
251     EXPECT_EQ([webView firstResponder], [webView textInputContentView]);
252     EXPECT_TRUE([[webView firstResponder].inputAssistantItem.leadingBarButtonGroups containsObject:leadingItems]);
253     EXPECT_TRUE([[webView firstResponder].inputAssistantItem.trailingBarButtonGroups containsObject:trailingItems]);
254
255     // Now blur and refocus the editable area, and check that the same leading and trailing button items are present.
256     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.blur()"];
257     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
258
259     EXPECT_EQ([webView firstResponder], [webView textInputContentView]);
260     EXPECT_TRUE([[webView firstResponder].inputAssistantItem.leadingBarButtonGroups containsObject:leadingItems]);
261     EXPECT_TRUE([[webView firstResponder].inputAssistantItem.trailingBarButtonGroups containsObject:trailingItems]);
262 }
263
264 TEST(KeyboardInputTests, OverrideInputAssistantItemBarButtonGroups)
265 {
266     auto inputDelegate = adoptNS([TestInputDelegate new]);
267     auto webView = adoptNS([[InputAssistantItemTestingWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
268     [webView _setInputDelegate:inputDelegate.get()];
269     [webView synchronouslyLoadHTMLString:@"<body contenteditable>"];
270     [inputDelegate setFocusStartsInputSessionPolicyHandler:[&] (WKWebView *, id <_WKFocusedElementInfo>) -> _WKFocusStartsInputSessionPolicy {
271         return _WKFocusStartsInputSessionPolicyAllow;
272     }];
273     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
274
275     UIBarButtonItemGroup *leadingItems = [InputAssistantItemTestingWebView leadingItemsForWebView:webView.get()];
276     UIBarButtonItemGroup *trailingItems = [InputAssistantItemTestingWebView trailingItemsForWebView:webView.get()];
277     EXPECT_EQ([webView firstResponder], [webView textInputContentView]);
278     EXPECT_TRUE([[webView firstResponder].inputAssistantItem.leadingBarButtonGroups containsObject:leadingItems]);
279     EXPECT_TRUE([[webView firstResponder].inputAssistantItem.trailingBarButtonGroups containsObject:trailingItems]);
280
281     // Now blur and refocus the editable area, and check that the same leading and trailing button items are present.
282     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.blur()"];
283     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
284
285     EXPECT_EQ([webView firstResponder], [webView textInputContentView]);
286     EXPECT_TRUE([[webView firstResponder].inputAssistantItem.leadingBarButtonGroups containsObject:leadingItems]);
287     EXPECT_TRUE([[webView firstResponder].inputAssistantItem.trailingBarButtonGroups containsObject:trailingItems]);
288 }
289
290 TEST(KeyboardInputTests, CustomInputViewAndInputAccessoryView)
291 {
292     auto inputView = adoptNS([[UIView alloc] init]);
293     auto inputAccessoryView = adoptNS([[UIView alloc] init]);
294     auto inputDelegate = adoptNS([TestInputDelegate new]);
295     [inputDelegate setWillStartInputSessionHandler:[inputView, inputAccessoryView] (WKWebView *, id<_WKFormInputSession> session) {
296         session.customInputView = inputView.get();
297         session.customInputAccessoryView = inputAccessoryView.get();
298     }];
299
300     auto webView = webViewWithAutofocusedInput(inputDelegate);
301     EXPECT_EQ(inputView.get(), [webView firstResponder].inputView);
302     EXPECT_EQ(inputAccessoryView.get(), [webView firstResponder].inputAccessoryView);
303 }
304
305 TEST(KeyboardInputTests, CanHandleKeyEventInCompletionHandler)
306 {
307     auto webView = webViewWithAutofocusedInput();
308     bool doneWaiting = false;
309
310     id <UITextInputPrivate> contentView = (id <UITextInputPrivate>)[webView firstResponder];
311     auto firstWebEvent = adoptNS([[WebEvent alloc] initWithKeyEventType:WebEventKeyDown timeStamp:CFAbsoluteTimeGetCurrent() characters:@"a" charactersIgnoringModifiers:@"a" modifiers:0 isRepeating:NO withFlags:0 withInputManagerHint:nil keyCode:0 isTabKey:NO]);
312     auto secondWebEvent = adoptNS([[WebEvent alloc] initWithKeyEventType:WebEventKeyUp timeStamp:CFAbsoluteTimeGetCurrent() characters:@"a" charactersIgnoringModifiers:@"a" modifiers:0 isRepeating:NO withFlags:0 withInputManagerHint:nil keyCode:0 isTabKey:NO]);
313     [contentView handleKeyWebEvent:firstWebEvent.get() withCompletionHandler:[&] (WebEvent *event, BOOL) {
314         EXPECT_TRUE([event isEqual:firstWebEvent.get()]);
315         [contentView handleKeyWebEvent:secondWebEvent.get() withCompletionHandler:[&] (WebEvent *event, BOOL) {
316             EXPECT_TRUE([event isEqual:secondWebEvent.get()]);
317             [contentView insertText:@"a"];
318             doneWaiting = true;
319         }];
320     }];
321
322     TestWebKitAPI::Util::run(&doneWaiting);
323     EXPECT_WK_STREQ("a", [webView stringByEvaluatingJavaScript:@"document.querySelector('input').value"]);
324 }
325
326 TEST(KeyboardInputTests, CaretSelectionRectAfterRestoringFirstResponderWithRetainActiveFocusedState)
327 {
328     auto expectedCaretRect = CGRectMake(16, 13, 2, 15);
329     auto webView = webViewWithAutofocusedInput();
330     EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
331     [webView waitForCaretViewFrameToBecome:expectedCaretRect];
332
333     dispatch_block_t restoreActiveFocusState = [webView _retainActiveFocusedState];
334     [webView resignFirstResponder];
335     restoreActiveFocusState();
336     [webView waitForCaretViewFrameToBecome:CGRectZero];
337
338     [webView becomeFirstResponder];
339     [webView waitForCaretViewFrameToBecome:expectedCaretRect];
340 }
341
342 TEST(KeyboardInputTests, RangedSelectionRectAfterRestoringFirstResponderWithRetainActiveFocusedState)
343 {
344     NSArray *expectedSelectionRects = @[ [NSValue valueWithCGRect:CGRectMake(16, 13, 24, 15)] ];
345
346     auto webView = webViewWithAutofocusedInput();
347     [[webView textInputContentView] insertText:@"hello"];
348     [webView selectAll:nil];
349     EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
350     [webView waitForSelectionViewRectsToBecome:expectedSelectionRects];
351
352     dispatch_block_t restoreActiveFocusState = [webView _retainActiveFocusedState];
353     [webView resignFirstResponder];
354     restoreActiveFocusState();
355     [webView waitForSelectionViewRectsToBecome:@[ ]];
356
357     [webView becomeFirstResponder];
358     [webView waitForSelectionViewRectsToBecome:expectedSelectionRects];
359 }
360
361 TEST(KeyboardInputTests, CaretSelectionRectAfterRestoringFirstResponder)
362 {
363     auto expectedCaretRect = CGRectMake(16, 13, 2, 15);
364     auto webView = webViewWithAutofocusedInput();
365     EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
366     [webView waitForCaretViewFrameToBecome:expectedCaretRect];
367
368     [webView resignFirstResponder];
369     [webView waitForCaretViewFrameToBecome:CGRectZero];
370
371     [webView becomeFirstResponder];
372     [webView waitForCaretViewFrameToBecome:expectedCaretRect];
373 }
374
375 TEST(KeyboardInputTests, RangedSelectionRectAfterRestoringFirstResponder)
376 {
377     NSArray *expectedSelectionRects = @[ [NSValue valueWithCGRect:CGRectMake(16, 13, 24, 15)] ];
378
379     auto webView = webViewWithAutofocusedInput();
380     [[webView textInputContentView] insertText:@"hello"];
381     [webView selectAll:nil];
382     EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
383     [webView waitForSelectionViewRectsToBecome:expectedSelectionRects];
384
385     [webView resignFirstResponder];
386     [webView waitForSelectionViewRectsToBecome:@[ ]];
387
388     [webView becomeFirstResponder];
389     [webView waitForSelectionViewRectsToBecome:expectedSelectionRects];
390 }
391
392 TEST(KeyboardInputTests, KeyboardTypeForInput)
393 {
394     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
395     auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
396
397     [inputDelegate setFocusStartsInputSessionPolicyHandler:[&] (WKWebView *, id <_WKFocusedElementInfo>) -> _WKFocusStartsInputSessionPolicy {
398         return _WKFocusStartsInputSessionPolicyAllow;
399     }];
400     [webView _setInputDelegate:inputDelegate.get()];
401     [webView synchronouslyLoadHTMLString:@"<input id='input'>"];
402     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"input.focus()"];
403
404     auto runTest = ^(NSString *inputType, NSString *inputMode, NSString *pattern, UIKeyboardType expectedKeyboardType) {
405         [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"input.blur()"];
406         [webView evaluateJavaScriptAndWaitForInputSessionToChange:[NSString stringWithFormat:@"input.type = '%@'; input.inputMode = '%@'; input.pattern = '%@'; input.focus()", inputType, inputMode, pattern]];
407
408         UIView<UITextInputPrivate> *textInput = (UIView<UITextInputPrivate> *)[webView textInputContentView];
409         UIKeyboardType keyboardType = [textInput textInputTraits].keyboardType;
410
411         bool success = keyboardType == expectedKeyboardType;
412         if (!success)
413             NSLog(@"Displayed %li for <input type='%@' inputmode='%@' pattern='%@'>. Expected %li.", (long)keyboardType, inputType, inputMode, pattern, (long)expectedKeyboardType);
414
415         return success;
416     };
417
418     NSDictionary *expectedKeyboardTypeForInputType = @{
419         @"text": @(UIKeyboardTypeDefault),
420         @"password": @(UIKeyboardTypeDefault),
421         @"search": @(UIKeyboardTypeDefault),
422         @"email": @(UIKeyboardTypeEmailAddress),
423         @"tel": @(UIKeyboardTypePhonePad),
424         @"number": @(UIKeyboardTypeNumbersAndPunctuation),
425         @"url": @(UIKeyboardTypeURL)
426     };
427
428     NSDictionary *expectedKeyboardTypeForInputMode = @{
429         @"": @(-1),
430         @"text": @(UIKeyboardTypeDefault),
431         @"tel": @(UIKeyboardTypePhonePad),
432         @"url": @(UIKeyboardTypeURL),
433         @"email": @(UIKeyboardTypeEmailAddress),
434         @"numeric": @(UIKeyboardTypeNumberPad),
435         @"decimal": @(UIKeyboardTypeDecimalPad),
436         @"search": @(UIKeyboardTypeWebSearch)
437     };
438
439     NSDictionary *expectedKeyboardTypeForPattern = @{
440         @"": @(-1),
441         @"\\\\d*": @(UIKeyboardTypeNumberPad),
442         @"[0-9]*": @(UIKeyboardTypeNumberPad)
443     };
444
445     for (NSString *inputType in expectedKeyboardTypeForInputType) {
446         BOOL isNumberOrTextInput = [inputType isEqual:@"text"] || [inputType isEqual:@"number"];
447         for (NSString *inputMode in expectedKeyboardTypeForInputMode) {
448             for (NSString *pattern in expectedKeyboardTypeForPattern) {
449                 NSNumber *keyboardType;
450                 if (inputMode.length) {
451                     // inputmode has the highest priority.
452                     keyboardType = expectedKeyboardTypeForInputMode[inputMode];
453                 } else {
454                     // Special case for text and number inputs that have a numeric pattern. Otherwise, the input type determines the keyboard type.
455                     keyboardType = pattern.length && isNumberOrTextInput ? expectedKeyboardTypeForPattern[pattern] : expectedKeyboardTypeForInputType[inputType];
456                 }
457
458                 EXPECT_TRUE(runTest(inputType, inputMode, pattern, (UIKeyboardType)keyboardType.intValue));
459             }
460         }
461     }
462 }
463
464 TEST(KeyboardInputTests, OverrideInputViewAndInputAccessoryView)
465 {
466     auto inputView = adoptNS([[UIView alloc] init]);
467     auto inputAccessoryView = adoptNS([[UIView alloc] init]);
468     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
469     auto webView = adoptNS([[CustomInputWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 568) configuration:configuration.get() inputView:inputView.get() inputAccessoryView:inputAccessoryView.get()]);
470     auto contentView = [webView textInputContentView];
471
472     EXPECT_EQ(inputAccessoryView.get(), [contentView inputAccessoryView]);
473     EXPECT_EQ(inputView.get(), [contentView inputView]);
474 }
475
476 TEST(KeyboardInputTests, DisableSmartQuotesAndDashes)
477 {
478     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
479     auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
480     [inputDelegate setFocusStartsInputSessionPolicyHandler:[&] (WKWebView *, id <_WKFocusedElementInfo>) -> _WKFocusStartsInputSessionPolicy {
481         return _WKFocusStartsInputSessionPolicyAllow;
482     }];
483     [webView _setInputDelegate:inputDelegate.get()];
484
485     auto checkSmartQuotesAndDashesType = [&] (UITextSmartDashesType dashesType, UITextSmartQuotesType quotesType) {
486         UITextInputTraits *traits = [[webView textInputContentView] textInputTraits];
487         EXPECT_EQ(dashesType, traits.smartDashesType);
488         EXPECT_EQ(quotesType, traits.smartQuotesType);
489     };
490
491     [webView synchronouslyLoadHTMLString:@"<div id='foo' contenteditable spellcheck='false'></div><textarea id='bar' spellcheck='false'></textarea><input id='baz' spellcheck='false'>"];
492     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"foo.focus()"];
493     checkSmartQuotesAndDashesType(UITextSmartDashesTypeNo, UITextSmartQuotesTypeNo);
494     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"bar.focus()"];
495     checkSmartQuotesAndDashesType(UITextSmartDashesTypeNo, UITextSmartQuotesTypeNo);
496     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"baz.focus()"];
497     checkSmartQuotesAndDashesType(UITextSmartDashesTypeNo, UITextSmartQuotesTypeNo);
498
499     [webView synchronouslyLoadHTMLString:@"<div id='foo' contenteditable></div><textarea id='bar' spellcheck='true'></textarea><input id='baz'>"];
500     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"foo.focus()"];
501     checkSmartQuotesAndDashesType(UITextSmartDashesTypeDefault, UITextSmartQuotesTypeDefault);
502     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"bar.focus()"];
503     checkSmartQuotesAndDashesType(UITextSmartDashesTypeDefault, UITextSmartQuotesTypeDefault);
504     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"baz.focus()"];
505     checkSmartQuotesAndDashesType(UITextSmartDashesTypeDefault, UITextSmartQuotesTypeDefault);
506 }
507
508 TEST(KeyboardInputTests, SelectionClipRectsWhenPresentingInputView)
509 {
510     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
511     auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
512     [inputDelegate setFocusStartsInputSessionPolicyHandler:[&] (WKWebView *, id <_WKFocusedElementInfo>) -> _WKFocusStartsInputSessionPolicy {
513         return _WKFocusStartsInputSessionPolicyAllow;
514     }];
515
516     CGRect selectionClipRect = CGRectNull;
517     [inputDelegate setDidStartInputSessionHandler:[&] (WKWebView *, id <_WKFormInputSession>) {
518         selectionClipRect = [[webView textInputContentView] _selectionClipRect];
519     }];
520     [webView _setInputDelegate:inputDelegate.get()];
521     [webView synchronouslyLoadHTMLString:@"<meta name='viewport' content='width=device-width, initial-scale=1'><input>"];
522     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.querySelector('input').focus()"];
523
524     EXPECT_EQ(11, selectionClipRect.origin.x);
525     EXPECT_EQ(11, selectionClipRect.origin.y);
526     EXPECT_EQ(134, selectionClipRect.size.width);
527     EXPECT_EQ(20, selectionClipRect.size.height);
528 }
529
530 TEST(KeyboardInputTests, TestWebViewAdditionalContextForStrongPasswordAssistance)
531 {
532     NSDictionary *expected = @{ @"strongPasswordAdditionalContext" : @"testUUID" };
533
534     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
535     auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
536
537     [inputDelegate setFocusStartsInputSessionPolicyHandler:[&] (WKWebView *, id <_WKFocusedElementInfo>) -> _WKFocusStartsInputSessionPolicy {
538         return _WKFocusStartsInputSessionPolicyAllow;
539     }];
540
541     [inputDelegate setWebViewAdditionalContextForStrongPasswordAssistanceHandler:[&] (WKWebView *) {
542         return expected;
543     }];
544
545     [inputDelegate setFocusRequiresStrongPasswordAssistanceHandler:[&] (WKWebView *, id <_WKFocusedElementInfo>) {
546         return YES;
547     }];
548
549     [webView _setInputDelegate:inputDelegate.get()];
550
551     [webView synchronouslyLoadHTMLString:@"<input type='password' id='input'>"];
552     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.getElementById('input').focus()"];
553
554     NSDictionary *actual = [[webView textInputContentView] _autofillContext];
555     EXPECT_TRUE([[actual allValues] containsObject:expected]);
556 }
557
558 } // namespace TestWebKitAPI
559
560 #endif // PLATFORM(IOS_FAMILY)