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