Allow pages to trigger programmatic paste from script on iOS
[WebKit-https.git] / Tools / WebKitTestRunner / ios / UIScriptControllerIOS.mm
1 /*
2  * Copyright (C) 2015-2019 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 #import "config.h"
27 #import "UIScriptController.h"
28
29 #if PLATFORM(IOS_FAMILY)
30
31 #import "HIDEventGenerator.h"
32 #import "PencilKitTestSPI.h"
33 #import "PlatformWebView.h"
34 #import "StringFunctions.h"
35 #import "TestController.h"
36 #import "TestRunnerWKWebView.h"
37 #import "UIKitSPI.h"
38 #import "UIScriptContext.h"
39 #import <JavaScriptCore/JavaScriptCore.h>
40 #import <JavaScriptCore/OpaqueJSString.h>
41 #import <UIKit/UIKit.h>
42 #import <WebCore/FloatRect.h>
43 #import <WebKit/WKWebViewPrivate.h>
44 #import <WebKit/WebKit.h>
45 #import <wtf/SoftLinking.h>
46 #import <wtf/Vector.h>
47
48 SOFT_LINK_FRAMEWORK(UIKit)
49 SOFT_LINK_CLASS(UIKit, UIPhysicalKeyboardEvent)
50
51 @interface UIPhysicalKeyboardEvent (UIPhysicalKeyboardEventHack)
52 @property (nonatomic, assign) NSInteger _modifierFlags;
53 @end
54
55 namespace WTR {
56
57 static NSDictionary *toNSDictionary(CGRect rect)
58 {
59     return @{
60         @"left": @(rect.origin.x),
61         @"top": @(rect.origin.y),
62         @"width": @(rect.size.width),
63         @"height": @(rect.size.height)
64     };
65 }
66
67 static unsigned arrayLength(JSContextRef context, JSObjectRef array)
68 {
69     auto lengthString = adopt(JSStringCreateWithUTF8CString("length"));
70     if (auto lengthValue = JSObjectGetProperty(context, array, lengthString.get(), nullptr))
71         return static_cast<unsigned>(JSValueToNumber(context, lengthValue, nullptr));
72     return 0;
73 }
74
75 static Vector<String> parseModifierArray(JSContextRef context, JSValueRef arrayValue)
76 {
77     if (!arrayValue)
78         return { };
79
80     // The value may either be a string with a single modifier or an array of modifiers.
81     if (JSValueIsString(context, arrayValue)) {
82         auto string = toWTFString(toWK(adopt(JSValueToStringCopy(context, arrayValue, nullptr))));
83         return { string };
84     }
85
86     if (!JSValueIsObject(context, arrayValue))
87         return { };
88     JSObjectRef array = const_cast<JSObjectRef>(arrayValue);
89     unsigned length = arrayLength(context, array);
90     Vector<String> modifiers;
91     modifiers.reserveInitialCapacity(length);
92     for (unsigned i = 0; i < length; ++i) {
93         JSValueRef exception = nullptr;
94         JSValueRef value = JSObjectGetPropertyAtIndex(context, array, i, &exception);
95         if (exception)
96             continue;
97         auto string = adopt(JSValueToStringCopy(context, value, &exception));
98         if (exception)
99             continue;
100         modifiers.append(toWTFString(toWK(string.get())));
101     }
102     return modifiers;
103 }
104
105 static BOOL forEachViewInHierarchy(UIView *view, void(^mapFunction)(UIView *subview, BOOL *stop))
106 {
107     BOOL stop = NO;
108     mapFunction(view, &stop);
109     if (stop)
110         return YES;
111
112     for (UIView *subview in view.subviews) {
113         stop = forEachViewInHierarchy(subview, mapFunction);
114         if (stop)
115             break;
116     }
117     return stop;
118 }
119
120 static UIView *findViewInHierarchyOfType(UIView *view, Class viewClass)
121 {
122     __block RetainPtr<UIView> foundView;
123     forEachViewInHierarchy(view, ^(UIView *subview, BOOL *stop) {
124         if (![subview isKindOfClass:viewClass])
125             return;
126
127         foundView = subview;
128         *stop = YES;
129     });
130     return foundView.autorelease();
131 }
132
133 static NSArray<UIView *> *findAllViewsInHierarchyOfType(UIView *view, Class viewClass)
134 {
135     __block RetainPtr<NSMutableArray> views = adoptNS([[NSMutableArray alloc] init]);
136     forEachViewInHierarchy(view, ^(UIView *subview, BOOL *stop) {
137         if ([subview isKindOfClass:viewClass])
138             [views addObject:subview];
139     });
140     return views.autorelease();
141 }
142
143 void UIScriptController::checkForOutstandingCallbacks()
144 {
145     if (![[HIDEventGenerator sharedHIDEventGenerator] checkForOutstandingCallbacks])
146         [NSException raise:@"WebKitTestRunnerTestProblem" format:@"The test completed before all synthesized events had been handled. Perhaps you're calling notifyDone() too early?"];
147 }
148
149 void UIScriptController::doAfterPresentationUpdate(JSValueRef callback)
150 {
151     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
152
153     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
154     [webView _doAfterNextPresentationUpdate:^{
155         if (!m_context)
156             return;
157         m_context->asyncTaskComplete(callbackID);
158     }];
159 }
160
161 void UIScriptController::doAfterNextStablePresentationUpdate(JSValueRef callback)
162 {
163     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
164
165     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
166     [webView _doAfterNextStablePresentationUpdate:^() {
167         if (m_context)
168             m_context->asyncTaskComplete(callbackID);
169     }];
170 }
171
172 void UIScriptController::doAfterVisibleContentRectUpdate(JSValueRef callback)
173 {
174     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
175
176     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
177     [webView _doAfterNextVisibleContentRectUpdate:^ {
178         if (!m_context)
179             return;
180         m_context->asyncTaskComplete(callbackID);
181     }];
182 }
183
184 void UIScriptController::zoomToScale(double scale, JSValueRef callback)
185 {
186     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
187
188     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
189
190     [webView zoomToScale:scale animated:YES completionHandler:^{
191         if (!m_context)
192             return;
193         m_context->asyncTaskComplete(callbackID);
194     }];
195 }
196
197 void UIScriptController::retrieveSpeakSelectionContent(JSValueRef callback)
198 {
199     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
200     
201     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
202     
203     [webView accessibilityRetrieveSpeakSelectionContentWithCompletionHandler:^() {
204         if (!m_context)
205             return;
206         m_context->asyncTaskComplete(callbackID);
207     }];
208 }
209
210 JSRetainPtr<JSStringRef> UIScriptController::accessibilitySpeakSelectionContent() const
211 {
212     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
213     return JSStringCreateWithCFString((CFStringRef)webView.accessibilitySpeakSelectionContent);
214 }
215
216 void UIScriptController::simulateAccessibilitySettingsChangeNotification(JSValueRef callback)
217 {
218     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
219
220     auto* webView = TestController::singleton().mainWebView()->platformView();
221     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
222     [center postNotificationName:UIAccessibilityInvertColorsStatusDidChangeNotification object:webView];
223
224     [webView _doAfterNextPresentationUpdate: ^{
225         if (!m_context)
226             return;
227         m_context->asyncTaskComplete(callbackID);
228     }];
229 }
230
231 double UIScriptController::zoomScale() const
232 {
233     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
234     return webView.scrollView.zoomScale;
235 }
236
237 static CGPoint globalToContentCoordinates(TestRunnerWKWebView *webView, long x, long y)
238 {
239     CGPoint point = CGPointMake(x, y);
240     point = [webView _convertPointFromContentsToView:point];
241     point = [webView convertPoint:point toView:nil];
242     point = [webView.window convertPoint:point toWindow:nil];
243     return point;
244 }
245
246 void UIScriptController::touchDownAtPoint(long x, long y, long touchCount, JSValueRef callback)
247 {
248     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
249
250     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
251     [[HIDEventGenerator sharedHIDEventGenerator] touchDown:location touchCount:touchCount completionBlock:^{
252         if (!m_context)
253             return;
254         m_context->asyncTaskComplete(callbackID);
255     }];
256 }
257
258 void UIScriptController::liftUpAtPoint(long x, long y, long touchCount, JSValueRef callback)
259 {
260     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
261     
262     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
263     [[HIDEventGenerator sharedHIDEventGenerator] liftUp:location touchCount:touchCount completionBlock:^{
264         if (!m_context)
265             return;
266         m_context->asyncTaskComplete(callbackID);
267     }];
268 }
269
270 void UIScriptController::singleTapAtPoint(long x, long y, JSValueRef callback)
271 {
272     singleTapAtPointWithModifiers(x, y, nullptr, callback);
273 }
274
275 void UIScriptController::singleTapAtPointWithModifiers(long x, long y, JSValueRef modifierArray, JSValueRef callback)
276 {
277     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
278
279     auto modifierFlags = parseModifierArray(m_context->jsContext(), modifierArray);
280     for (auto& modifierFlag : modifierFlags)
281         [[HIDEventGenerator sharedHIDEventGenerator] keyDown:modifierFlag];
282
283     [[HIDEventGenerator sharedHIDEventGenerator] tap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
284         if (!m_context)
285             return;
286         for (size_t i = modifierFlags.size(); i; ) {
287             --i;
288             [[HIDEventGenerator sharedHIDEventGenerator] keyUp:modifierFlags[i]];
289         }
290         [[HIDEventGenerator sharedHIDEventGenerator] sendMarkerHIDEventWithCompletionBlock:^{
291             if (!m_context)
292                 return;
293             m_context->asyncTaskComplete(callbackID);
294         }];
295     }];
296 }
297
298 void UIScriptController::doubleTapAtPoint(long x, long y, JSValueRef callback)
299 {
300     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
301
302     [[HIDEventGenerator sharedHIDEventGenerator] doubleTap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
303         if (!m_context)
304             return;
305         m_context->asyncTaskComplete(callbackID);
306     }];
307 }
308
309 void UIScriptController::stylusDownAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
310 {
311     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
312
313     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
314     [[HIDEventGenerator sharedHIDEventGenerator] stylusDownAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
315         if (!m_context)
316             return;
317         m_context->asyncTaskComplete(callbackID);
318     }];
319 }
320
321 void UIScriptController::stylusMoveToPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
322 {
323     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
324
325     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
326     [[HIDEventGenerator sharedHIDEventGenerator] stylusMoveToPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
327         if (!m_context)
328             return;
329         m_context->asyncTaskComplete(callbackID);
330     }];
331 }
332
333 void UIScriptController::stylusUpAtPoint(long x, long y, JSValueRef callback)
334 {
335     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
336
337     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
338     [[HIDEventGenerator sharedHIDEventGenerator] stylusUpAtPoint:location completionBlock:^{
339         if (!m_context)
340             return;
341         m_context->asyncTaskComplete(callbackID);
342     }];
343 }
344
345 void UIScriptController::stylusTapAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
346 {
347     stylusTapAtPointWithModifiers(x, y, azimuthAngle, altitudeAngle, pressure, nullptr, callback);
348 }
349
350 void UIScriptController::stylusTapAtPointWithModifiers(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef modifierArray, JSValueRef callback)
351 {
352     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
353
354     auto modifierFlags = parseModifierArray(m_context->jsContext(), modifierArray);
355     for (auto& modifierFlag : modifierFlags)
356         [[HIDEventGenerator sharedHIDEventGenerator] keyDown:modifierFlag];
357
358     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
359     [[HIDEventGenerator sharedHIDEventGenerator] stylusTapAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
360         if (!m_context)
361             return;
362         for (size_t i = modifierFlags.size(); i; ) {
363             --i;
364             [[HIDEventGenerator sharedHIDEventGenerator] keyUp:modifierFlags[i]];
365         }
366         [[HIDEventGenerator sharedHIDEventGenerator] sendMarkerHIDEventWithCompletionBlock:^{
367             if (!m_context)
368                 return;
369             m_context->asyncTaskComplete(callbackID);
370         }];
371     }];
372 }
373
374 void convertCoordinates(NSMutableDictionary *event)
375 {
376     if (event[HIDEventTouchesKey]) {
377         for (NSMutableDictionary *touch in event[HIDEventTouchesKey]) {
378             auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), (long)[touch[HIDEventXKey] doubleValue], (long)[touch[HIDEventYKey]doubleValue]);
379             touch[HIDEventXKey] = @(location.x);
380             touch[HIDEventYKey] = @(location.y);
381         }
382     }
383 }
384
385 void UIScriptController::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
386 {
387     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
388
389     String jsonString = eventsJSON->string();
390     auto eventInfo = dynamic_objc_cast<NSDictionary>([NSJSONSerialization JSONObjectWithData:[(NSString *)jsonString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves error:nil]);
391     
392     for (NSMutableDictionary *event in eventInfo[TopLevelEventInfoKey]) {
393         if (![event[HIDEventCoordinateSpaceKey] isEqualToString:HIDEventCoordinateSpaceTypeContent])
394             continue;
395         
396         if (event[HIDEventStartEventKey])
397             convertCoordinates(event[HIDEventStartEventKey]);
398         
399         if (event[HIDEventEndEventKey])
400             convertCoordinates(event[HIDEventEndEventKey]);
401         
402         if (event[HIDEventTouchesKey])
403             convertCoordinates(event);
404     }
405     
406     if (!eventInfo || ![eventInfo isKindOfClass:[NSDictionary class]]) {
407         WTFLogAlways("JSON is not convertible to a dictionary");
408         return;
409     }
410     
411     [[HIDEventGenerator sharedHIDEventGenerator] sendEventStream:eventInfo completionBlock:^{
412         if (!m_context)
413             return;
414         m_context->asyncTaskComplete(callbackID);
415     }];
416 }
417
418 void UIScriptController::dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, JSValueRef callback)
419 {
420     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
421
422     CGPoint startPoint = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), startX, startY);
423     CGPoint endPoint = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), endX, endY);
424     
425     [[HIDEventGenerator sharedHIDEventGenerator] dragWithStartPoint:startPoint endPoint:endPoint duration:durationSeconds completionBlock:^{
426         if (!m_context)
427             return;
428         m_context->asyncTaskComplete(callbackID);
429     }];
430 }
431     
432 void UIScriptController::longPressAtPoint(long x, long y, JSValueRef callback)
433 {
434     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
435     
436     [[HIDEventGenerator sharedHIDEventGenerator] longPress:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
437         if (!m_context)
438             return;
439         m_context->asyncTaskComplete(callbackID);
440     }];
441 }
442
443 void UIScriptController::enterText(JSStringRef text)
444 {
445     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
446     auto textAsCFString = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, text));
447     [webView _simulateTextEntered:(NSString *)textAsCFString.get()];
448 }
449
450 void UIScriptController::typeCharacterUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
451 {
452     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
453
454     // Assumes that the keyboard is already shown.
455     [[HIDEventGenerator sharedHIDEventGenerator] keyPress:toWTFString(toWK(character)) completionBlock:^{
456         if (!m_context)
457             return;
458         m_context->asyncTaskComplete(callbackID);
459     }];
460 }
461
462 static UIPhysicalKeyboardEvent *createUIPhysicalKeyboardEvent(NSString *hidInputString, NSString *uiEventInputString, UIKeyModifierFlags modifierFlags, UIKeyboardInputFlags inputFlags, bool isKeyDown)
463 {
464     auto* keyboardEvent = [getUIPhysicalKeyboardEventClass() _eventWithInput:uiEventInputString inputFlags:inputFlags];
465     keyboardEvent._modifierFlags = modifierFlags;
466     auto hidEvent = createHIDKeyEvent(hidInputString, keyboardEvent.timestamp, isKeyDown);
467     [keyboardEvent _setHIDEvent:hidEvent.get() keyboard:nullptr];
468     return keyboardEvent;
469 }
470
471 void UIScriptController::keyDown(JSStringRef character, JSValueRef modifierArray)
472 {
473     // Character can be either a single Unicode code point or the name of a special key (e.g. "downArrow").
474     // HIDEventGenerator knows how to map these special keys to the appropriate keycode.
475     String inputString = toWTFString(toWK(character));
476     auto modifierFlags = parseModifierArray(m_context->jsContext(), modifierArray);
477
478     for (auto& modifierFlag : modifierFlags)
479         [[HIDEventGenerator sharedHIDEventGenerator] keyDown:modifierFlag];
480
481     [[HIDEventGenerator sharedHIDEventGenerator] keyDown:inputString];
482     [[HIDEventGenerator sharedHIDEventGenerator] keyUp:inputString];
483
484     for (size_t i = modifierFlags.size(); i; ) {
485         --i;
486         [[HIDEventGenerator sharedHIDEventGenerator] keyUp:modifierFlags[i]];
487     }
488
489     [[HIDEventGenerator sharedHIDEventGenerator] sendMarkerHIDEventWithCompletionBlock:^{ /* Do nothing */ }];
490 }
491
492 void UIScriptController::dismissFormAccessoryView()
493 {
494     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
495     [webView dismissFormAccessoryView];
496 }
497
498 JSRetainPtr<JSStringRef> UIScriptController::selectFormPopoverTitle() const
499 {
500     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
501     return JSStringCreateWithCFString((CFStringRef)webView.selectFormPopoverTitle);
502 }
503
504 JSRetainPtr<JSStringRef> UIScriptController::textContentType() const
505 {
506     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
507     return JSStringCreateWithCFString((CFStringRef)(webView.textContentTypeForTesting ?: @""));
508 }
509
510 JSRetainPtr<JSStringRef> UIScriptController::formInputLabel() const
511 {
512     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
513     return JSStringCreateWithCFString((CFStringRef)webView.formInputLabel);
514 }
515
516 void UIScriptController::selectFormAccessoryPickerRow(long rowIndex)
517 {
518     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
519     [webView selectFormAccessoryPickerRow:rowIndex];
520 }
521
522 void UIScriptController::setTimePickerValue(long hour, long minute)
523 {
524     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
525     [webView setTimePickerValueToHour:hour minute:minute];
526 }
527
528 bool UIScriptController::isPresentingModally() const
529 {
530     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
531     return !!webView.window.rootViewController.presentedViewController;
532 }
533
534 static CGPoint contentOffsetBoundedInValidRange(UIScrollView *scrollView, CGPoint contentOffset)
535 {
536     UIEdgeInsets contentInsets = scrollView.contentInset;
537     CGSize contentSize = scrollView.contentSize;
538     CGSize scrollViewSize = scrollView.bounds.size;
539
540     CGFloat maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width;
541     contentOffset.x = std::min(maxHorizontalOffset, contentOffset.x);
542     contentOffset.x = std::max(-contentInsets.left, contentOffset.x);
543
544     CGFloat maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height;
545     contentOffset.y = std::min(maxVerticalOffset, contentOffset.y);
546     contentOffset.y = std::max(-contentInsets.top, contentOffset.y);
547     return contentOffset;
548 }
549
550 double UIScriptController::contentOffsetX() const
551 {
552     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
553     return webView.scrollView.contentOffset.x;
554 }
555
556 double UIScriptController::contentOffsetY() const
557 {
558     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
559     return webView.scrollView.contentOffset.y;
560 }
561
562 void UIScriptController::scrollToOffset(long x, long y)
563 {
564     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
565     [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:YES];
566 }
567
568 void UIScriptController::immediateScrollToOffset(long x, long y)
569 {
570     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
571     [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:NO];
572 }
573
574 void UIScriptController::immediateZoomToScale(double scale)
575 {
576     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
577     [webView.scrollView setZoomScale:scale animated:NO];
578 }
579
580 void UIScriptController::keyboardAccessoryBarNext()
581 {
582     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
583     [webView keyboardAccessoryBarNext];
584 }
585
586 void UIScriptController::keyboardAccessoryBarPrevious()
587 {
588     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
589     [webView keyboardAccessoryBarPrevious];
590 }
591
592 bool UIScriptController::isShowingKeyboard() const
593 {
594     return TestController::singleton().mainWebView()->platformView().showingKeyboard;
595 }
596
597 void UIScriptController::applyAutocorrection(JSStringRef newString, JSStringRef oldString, JSValueRef callback)
598 {
599     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
600
601     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
602     [webView applyAutocorrection:toWTFString(toWK(newString)) toString:toWTFString(toWK(oldString)) withCompletionHandler:^ {
603         // applyAutocorrection can call its completion handler synchronously,
604         // which makes UIScriptController unhappy (see bug 172884).
605         dispatch_async(dispatch_get_main_queue(), ^ {
606             if (!m_context)
607                 return;
608             m_context->asyncTaskComplete(callbackID);
609         });
610     }];
611 }
612
613 double UIScriptController::minimumZoomScale() const
614 {
615     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
616     return webView.scrollView.minimumZoomScale;
617 }
618
619 double UIScriptController::maximumZoomScale() const
620 {
621     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
622     return webView.scrollView.maximumZoomScale;
623 }
624
625 Optional<bool> UIScriptController::stableStateOverride() const
626 {
627     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
628     if (webView._stableStateOverride)
629         return webView._stableStateOverride.boolValue;
630
631     return WTF::nullopt;
632 }
633
634 void UIScriptController::setStableStateOverride(Optional<bool> overrideValue)
635 {
636     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
637     if (overrideValue)
638         webView._stableStateOverride = @(overrideValue.value());
639     else
640         webView._stableStateOverride = nil;
641 }
642
643 JSObjectRef UIScriptController::contentVisibleRect() const
644 {
645     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
646
647     CGRect contentVisibleRect = webView._contentVisibleRect;
648     
649     WebCore::FloatRect rect(contentVisibleRect.origin.x, contentVisibleRect.origin.y, contentVisibleRect.size.width, contentVisibleRect.size.height);
650     return m_context->objectFromRect(rect);
651 }
652
653 JSObjectRef UIScriptController::textSelectionRangeRects() const
654 {
655     auto selectionRects = adoptNS([[NSMutableArray alloc] init]);
656     NSArray *rects = TestController::singleton().mainWebView()->platformView()._uiTextSelectionRects;
657     for (NSValue *rect in rects)
658         [selectionRects addObject:toNSDictionary(rect.CGRectValue)];
659
660     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:selectionRects.get() inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
661 }
662
663 JSObjectRef UIScriptController::textSelectionCaretRect() const
664 {
665     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(TestController::singleton().mainWebView()->platformView()._uiTextCaretRect) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
666 }
667
668 JSObjectRef UIScriptController::selectionStartGrabberViewRect() const
669 {
670     UIView *contentView = platformContentView();
671     UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
672     auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"startGrabber"] frame] toView:contentView];
673     frameInContentCoordinates = CGRectIntersection(contentView.bounds, frameInContentCoordinates);
674     auto jsContext = m_context->jsContext();
675     return JSValueToObject(jsContext, [JSValue valueWithObject:toNSDictionary(frameInContentCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:jsContext]].JSValueRef, nullptr);
676 }
677
678 JSObjectRef UIScriptController::selectionEndGrabberViewRect() const
679 {
680     UIView *contentView = platformContentView();
681     UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
682     auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"endGrabber"] frame] toView:contentView];
683     frameInContentCoordinates = CGRectIntersection(contentView.bounds, frameInContentCoordinates);
684     auto jsContext = m_context->jsContext();
685     return JSValueToObject(jsContext, [JSValue valueWithObject:toNSDictionary(frameInContentCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:jsContext]].JSValueRef, nullptr);
686 }
687
688 JSObjectRef UIScriptController::selectionCaretViewRect() const
689 {
690     UIView *contentView = platformContentView();
691     UIView *caretView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.caretView"];
692     auto rectInContentViewCoordinates = CGRectIntersection([caretView convertRect:caretView.bounds toView:contentView], contentView.bounds);
693     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(rectInContentViewCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
694 }
695
696 JSObjectRef UIScriptController::selectionRangeViewRects() const
697 {
698     UIView *contentView = platformContentView();
699     UIView *rangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
700     auto rectsAsDictionaries = adoptNS([[NSMutableArray alloc] init]);
701     NSArray *textRectInfoArray = [rangeView valueForKeyPath:@"rects"];
702     for (id textRectInfo in textRectInfoArray) {
703         NSValue *rectValue = [textRectInfo valueForKeyPath:@"rect"];
704         auto rangeRectInContentViewCoordinates = [rangeView convertRect:rectValue.CGRectValue toView:contentView];
705         [rectsAsDictionaries addObject:toNSDictionary(CGRectIntersection(rangeRectInContentViewCoordinates, contentView.bounds))];
706     }
707     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:rectsAsDictionaries.get() inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
708 }
709
710 JSObjectRef UIScriptController::inputViewBounds() const
711 {
712     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(TestController::singleton().mainWebView()->platformView()._inputViewBounds) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
713 }
714
715 void UIScriptController::removeAllDynamicDictionaries()
716 {
717     [UIKeyboard removeAllDynamicDictionaries];
718 }
719
720 JSRetainPtr<JSStringRef> UIScriptController::scrollingTreeAsText() const
721 {
722     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
723     return JSStringCreateWithCFString((CFStringRef)[webView _scrollingTreeAsText]);
724 }
725
726 JSObjectRef UIScriptController::propertiesOfLayerWithID(uint64_t layerID) const
727 {
728     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:[TestController::singleton().mainWebView()->platformView() _propertiesOfLayerWithID:layerID] inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
729 }
730
731 static UIDeviceOrientation toUIDeviceOrientation(DeviceOrientation* orientation)
732 {
733     if (!orientation)
734         return UIDeviceOrientationPortrait;
735         
736     switch (*orientation) {
737     case DeviceOrientation::Portrait:
738         return UIDeviceOrientationPortrait;
739     case DeviceOrientation::PortraitUpsideDown:
740         return UIDeviceOrientationPortraitUpsideDown;
741     case DeviceOrientation::LandscapeLeft:
742         return UIDeviceOrientationLandscapeLeft;
743     case DeviceOrientation::LandscapeRight:
744         return UIDeviceOrientationLandscapeRight;
745     }
746     
747     return UIDeviceOrientationPortrait;
748 }
749
750 void UIScriptController::simulateRotation(DeviceOrientation* orientation, JSValueRef callback)
751 {
752     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
753     webView.usesSafariLikeRotation = NO;
754     
755     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
756     
757     webView.rotationDidEndCallback = ^{
758         if (!m_context)
759             return;
760         m_context->asyncTaskComplete(callbackID);
761     };
762     
763     [[UIDevice currentDevice] setOrientation:toUIDeviceOrientation(orientation) animated:YES];
764 }
765
766 void UIScriptController::simulateRotationLikeSafari(DeviceOrientation* orientation, JSValueRef callback)
767 {
768     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
769     webView.usesSafariLikeRotation = YES;
770     
771     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
772     
773     webView.rotationDidEndCallback = ^{
774         if (!m_context)
775             return;
776         m_context->asyncTaskComplete(callbackID);
777     };
778     
779     [[UIDevice currentDevice] setOrientation:toUIDeviceOrientation(orientation) animated:YES];
780 }
781
782 void UIScriptController::platformSetDidStartFormControlInteractionCallback()
783 {
784     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
785     webView.didStartFormControlInteractionCallback = ^{
786         if (!m_context)
787             return;
788         m_context->fireCallback(CallbackTypeDidStartFormControlInteraction);
789     };
790 }
791
792 void UIScriptController::platformSetDidEndFormControlInteractionCallback()
793 {
794     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
795     webView.didEndFormControlInteractionCallback = ^{
796         if (!m_context)
797             return;
798         m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
799     };
800 }
801     
802 void UIScriptController::platformSetDidShowForcePressPreviewCallback()
803 {
804     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
805     webView.didShowForcePressPreviewCallback = ^ {
806         if (!m_context)
807             return;
808         m_context->fireCallback(CallbackTypeDidShowForcePressPreview);
809     };
810 }
811
812 void UIScriptController::platformSetDidDismissForcePressPreviewCallback()
813 {
814     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
815     webView.didDismissForcePressPreviewCallback = ^ {
816         if (!m_context)
817             return;
818         m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
819     };
820 }
821
822 void UIScriptController::platformSetWillBeginZoomingCallback()
823 {
824     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
825     webView.willBeginZoomingCallback = ^{
826         if (!m_context)
827             return;
828         m_context->fireCallback(CallbackTypeWillBeginZooming);
829     };
830 }
831
832 void UIScriptController::platformSetDidEndZoomingCallback()
833 {
834     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
835     webView.didEndZoomingCallback = ^{
836         if (!m_context)
837             return;
838         m_context->fireCallback(CallbackTypeDidEndZooming);
839     };
840 }
841
842 void UIScriptController::platformSetDidShowKeyboardCallback()
843 {
844     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
845     webView.didShowKeyboardCallback = ^{
846         if (!m_context)
847             return;
848         m_context->fireCallback(CallbackTypeDidShowKeyboard);
849     };
850 }
851
852 void UIScriptController::platformSetDidHideKeyboardCallback()
853 {
854     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
855     webView.didHideKeyboardCallback = ^{
856         if (!m_context)
857             return;
858         m_context->fireCallback(CallbackTypeDidHideKeyboard);
859     };
860 }
861
862 void UIScriptController::platformSetDidShowMenuCallback()
863 {
864     TestController::singleton().mainWebView()->platformView().didShowMenuCallback = ^{
865         if (!m_context)
866             return;
867         m_context->fireCallback(CallbackTypeDidShowMenu);
868     };
869 }
870
871 void UIScriptController::platformSetDidHideMenuCallback()
872 {
873     TestController::singleton().mainWebView()->platformView().didHideMenuCallback = ^{
874         if (!m_context)
875             return;
876         m_context->fireCallback(CallbackTypeDidHideMenu);
877     };
878 }
879
880 JSObjectRef UIScriptController::rectForMenuAction(JSStringRef jsAction) const
881 {
882     auto action = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, jsAction));
883
884     UIWindow *windowForButton = nil;
885     UIButton *buttonForAction = nil;
886     for (UIWindow *window in UIApplication.sharedApplication.windows) {
887         if (![window isKindOfClass:UITextEffectsWindow.class])
888             continue;
889
890         UIView *calloutBar = findViewInHierarchyOfType(window, UICalloutBar.class);
891         if (!calloutBar)
892             continue;
893
894         for (UIButton *button in findAllViewsInHierarchyOfType(calloutBar, UIButton.class)) {
895             NSString *buttonTitle = [button titleForState:UIControlStateNormal];
896             if (!buttonTitle.length)
897                 continue;
898
899             if (![buttonTitle isEqualToString:(__bridge NSString *)action.get()])
900                 continue;
901
902             buttonForAction = button;
903             windowForButton = window;
904         }
905     }
906
907     if (!buttonForAction)
908         return nullptr;
909
910     CGRect rectInRootViewCoordinates = [buttonForAction convertRect:buttonForAction.bounds toView:platformContentView()];
911     return m_context->objectFromRect(WebCore::FloatRect(rectInRootViewCoordinates.origin.x, rectInRootViewCoordinates.origin.y, rectInRootViewCoordinates.size.width, rectInRootViewCoordinates.size.height));
912 }
913
914 void UIScriptController::platformSetDidEndScrollingCallback()
915 {
916     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
917     webView.didEndScrollingCallback = ^{
918         if (!m_context)
919             return;
920         m_context->fireCallback(CallbackTypeDidEndScrolling);
921     };
922 }
923
924 void UIScriptController::platformClearAllCallbacks()
925 {
926     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
927     
928     webView.didStartFormControlInteractionCallback = nil;
929     webView.didEndFormControlInteractionCallback = nil;
930     webView.didShowForcePressPreviewCallback = nil;
931     webView.didDismissForcePressPreviewCallback = nil;
932     webView.didEndZoomingCallback = nil;
933     webView.willBeginZoomingCallback = nil;
934     webView.didHideKeyboardCallback = nil;
935     webView.didShowKeyboardCallback = nil;
936     webView.didEndScrollingCallback = nil;
937     webView.rotationDidEndCallback = nil;
938 }
939
940 void UIScriptController::setSafeAreaInsets(double top, double right, double bottom, double left)
941 {
942     UIEdgeInsets insets = UIEdgeInsetsMake(top, left, bottom, right);
943     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
944     webView.overrideSafeAreaInsets = insets;
945 }
946
947 void UIScriptController::beginBackSwipe(JSValueRef callback)
948 {
949     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
950     [webView _beginBackSwipeForTesting];
951 }
952
953 void UIScriptController::completeBackSwipe(JSValueRef callback)
954 {
955     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
956     [webView _completeBackSwipeForTesting];
957 }
958
959 bool UIScriptController::isShowingDataListSuggestions() const
960 {
961     Class remoteKeyboardWindowClass = NSClassFromString(@"UIRemoteKeyboardWindow");
962     Class suggestionsPickerViewClass = NSClassFromString(@"WKDataListSuggestionsPickerView");
963     UIWindow *remoteInputHostingWindow = nil;
964     for (UIWindow *window in UIApplication.sharedApplication.windows) {
965         if ([window isKindOfClass:remoteKeyboardWindowClass])
966             remoteInputHostingWindow = window;
967     }
968
969     if (!remoteInputHostingWindow)
970         return false;
971
972     __block bool foundDataListSuggestionsPickerView = false;
973     forEachViewInHierarchy(remoteInputHostingWindow, ^(UIView *subview, BOOL *stop) {
974         if (![subview isKindOfClass:suggestionsPickerViewClass])
975             return;
976
977         foundDataListSuggestionsPickerView = true;
978         *stop = YES;
979     });
980     return foundDataListSuggestionsPickerView;
981 }
982
983 #if HAVE(PENCILKIT)
984 static PKCanvasView *findEditableImageCanvas()
985 {
986     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
987     Class pkCanvasViewClass = NSClassFromString(@"PKCanvasView");
988     __block PKCanvasView *canvasView = nil;
989     forEachViewInHierarchy(webView.window, ^(UIView *subview, BOOL *stop) {
990         if (![subview isKindOfClass:pkCanvasViewClass])
991             return;
992
993         canvasView = (PKCanvasView *)subview;
994         *stop = YES;
995     });
996     return canvasView;
997 }
998 #endif
999
1000 void UIScriptController::drawSquareInEditableImage()
1001 {
1002 #if HAVE(PENCILKIT)
1003     Class pkDrawingClass = NSClassFromString(@"PKDrawing");
1004     Class pkInkClass = NSClassFromString(@"PKInk");
1005     Class pkStrokeClass = NSClassFromString(@"PKStroke");
1006
1007     PKCanvasView *canvasView = findEditableImageCanvas();
1008     RetainPtr<PKDrawing> drawing = canvasView.drawing ?: adoptNS([[pkDrawingClass alloc] init]);
1009     RetainPtr<CGPathRef> path = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, 50, 50), NULL));
1010     RetainPtr<PKInk> ink = [pkInkClass inkWithType:0 color:UIColor.greenColor weight:100.0];
1011     RetainPtr<PKStroke> stroke = adoptNS([[pkStrokeClass alloc] _initWithPath:path.get() ink:ink.get() inputScale:1]);
1012     [drawing _addStroke:stroke.get()];
1013
1014     [canvasView setDrawing:drawing.get()];
1015 #endif
1016 }
1017
1018 long UIScriptController::numberOfStrokesInEditableImage()
1019 {
1020 #if HAVE(PENCILKIT)
1021     PKCanvasView *canvasView = findEditableImageCanvas();
1022     return canvasView.drawing._allStrokes.count;
1023 #else
1024     return 0;
1025 #endif
1026 }
1027
1028 void UIScriptController::setKeyboardInputModeIdentifier(JSStringRef identifier)
1029 {
1030     TestController::singleton().setKeyboardInputModeIdentifier(toWTFString(toWK(identifier)));
1031 }
1032
1033 // FIXME: Write this in terms of HIDEventGenerator once we know how to reset caps lock state
1034 // on test completion to avoid it effecting subsequent tests.
1035 void UIScriptController::toggleCapsLock(JSValueRef callback)
1036 {
1037     m_capsLockOn = !m_capsLockOn;
1038     auto *keyboardEvent = createUIPhysicalKeyboardEvent(@"capsLock", [NSString string], m_capsLockOn ? UIKeyModifierAlphaShift : 0,
1039         kUIKeyboardInputModifierFlagsChanged, m_capsLockOn);
1040     [[UIApplication sharedApplication] handleKeyUIEvent:keyboardEvent];
1041     doAsyncTask(callback);
1042 }
1043
1044 JSObjectRef UIScriptController::attachmentInfo(JSStringRef jsAttachmentIdentifier)
1045 {
1046     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
1047
1048     auto attachmentIdentifier = toWTFString(toWK(jsAttachmentIdentifier));
1049     _WKAttachment *attachment = [webView _attachmentForIdentifier:attachmentIdentifier];
1050     _WKAttachmentInfo *attachmentInfo = attachment.info;
1051
1052     NSDictionary *attachmentInfoDictionary = @{
1053         @"id": attachmentIdentifier,
1054         @"name": attachmentInfo.name,
1055         @"contentType": attachmentInfo.contentType,
1056         @"filePath": attachmentInfo.filePath,
1057         @"size": @(attachmentInfo.data.length),
1058     };
1059
1060     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:attachmentInfoDictionary inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
1061 }
1062
1063 UIView *UIScriptController::platformContentView() const
1064 {
1065     return [TestController::singleton().mainWebView()->platformView() valueForKeyPath:@"_currentContentView"];
1066 }
1067
1068 JSObjectRef UIScriptController::calendarType() const
1069 {
1070     WKWebView *webView = TestController::singleton().mainWebView()->platformView();
1071     UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
1072     NSString *calendarTypeString = [contentView valueForKeyPath:@"formInputControl.dateTimePickerCalendarType"];
1073     auto jsContext = m_context->jsContext();
1074     return JSValueToObject(jsContext, [JSValue valueWithObject:calendarTypeString inContext:[JSContext contextWithJSGlobalContextRef:jsContext]].JSValueRef, nullptr);
1075 }
1076
1077 }
1078
1079 #endif // PLATFORM(IOS_FAMILY)