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