3e437fa14024181d30cf3d0207bd9382d6eae072
[WebKit-https.git] / Tools / WebKitTestRunner / ios / UIScriptControllerIOS.mm
1 /*
2  * Copyright (C) 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "UIScriptController.h"
28
29 #if PLATFORM(IOS)
30
31 #import "HIDEventGenerator.h"
32 #import "PlatformWebView.h"
33 #import "StringFunctions.h"
34 #import "TestController.h"
35 #import "TestRunnerWKWebView.h"
36 #import "UIKitTestSPI.h"
37 #import "UIScriptContext.h"
38 #import <JavaScriptCore/JavaScriptCore.h>
39 #import <JavaScriptCore/OpaqueJSString.h>
40 #import <UIKit/UIKit.h>
41 #import <WebCore/FloatRect.h>
42 #import <WebKit/WKWebViewPrivate.h>
43 #import <WebKit/WebKit.h>
44
45 namespace WTR {
46
47 static NSDictionary *toNSDictionary(CGRect rect)
48 {
49     return @{
50         @"left": @(rect.origin.x),
51         @"top": @(rect.origin.y),
52         @"width": @(rect.size.width),
53         @"height": @(rect.size.height)
54     };
55 }
56     
57 void UIScriptController::checkForOutstandingCallbacks()
58 {
59     if (![[HIDEventGenerator sharedHIDEventGenerator] checkForOutstandingCallbacks])
60         [NSException raise:@"WebKitTestRunnerTestProblem" format:@"The test completed before all synthesized events had been handled. Perhaps you're calling notifyDone() too early?"];
61 }
62
63 void UIScriptController::doAsyncTask(JSValueRef callback)
64 {
65     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
66
67     dispatch_async(dispatch_get_main_queue(), ^{
68         if (!m_context)
69             return;
70         m_context->asyncTaskComplete(callbackID);
71     });
72 }
73
74 void UIScriptController::doAfterPresentationUpdate(JSValueRef callback)
75 {
76     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
77
78     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
79     [webView _doAfterNextPresentationUpdate:^{
80         if (!m_context)
81             return;
82         m_context->asyncTaskComplete(callbackID);
83     }];
84 }
85
86 void UIScriptController::doAfterNextStablePresentationUpdate(JSValueRef callback)
87 {
88     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
89
90     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
91     [webView _doAfterNextStablePresentationUpdate:^() {
92         if (m_context)
93             m_context->asyncTaskComplete(callbackID);
94     }];
95 }
96
97 void UIScriptController::doAfterVisibleContentRectUpdate(JSValueRef callback)
98 {
99     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
100
101     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
102     [webView _doAfterNextVisibleContentRectUpdate:^ {
103         if (!m_context)
104             return;
105         m_context->asyncTaskComplete(callbackID);
106     }];
107 }
108
109 void UIScriptController::zoomToScale(double scale, JSValueRef callback)
110 {
111     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
112
113     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
114
115     [webView zoomToScale:scale animated:YES completionHandler:^{
116         if (!m_context)
117             return;
118         m_context->asyncTaskComplete(callbackID);
119     }];
120 }
121
122 void UIScriptController::retrieveSpeakSelectionContent(JSValueRef callback)
123 {
124     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
125     
126     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
127     
128     [webView accessibilityRetrieveSpeakSelectionContentWithCompletionHandler:^() {
129         if (!m_context)
130             return;
131         m_context->asyncTaskComplete(callbackID);
132     }];
133 }
134
135 JSRetainPtr<JSStringRef> UIScriptController::accessibilitySpeakSelectionContent() const
136 {
137     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
138     return JSStringCreateWithCFString((CFStringRef)webView.accessibilitySpeakSelectionContent);
139 }
140
141 void UIScriptController::simulateAccessibilitySettingsChangeNotification(JSValueRef callback)
142 {
143     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
144
145     auto* webView = TestController::singleton().mainWebView()->platformView();
146     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
147     [center postNotificationName:UIAccessibilityInvertColorsStatusDidChangeNotification object:webView];
148
149     [webView _doAfterNextPresentationUpdate: ^{
150         if (!m_context)
151             return;
152         m_context->asyncTaskComplete(callbackID);
153     }];
154 }
155
156 double UIScriptController::zoomScale() const
157 {
158     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
159     return webView.scrollView.zoomScale;
160 }
161
162 static CGPoint globalToContentCoordinates(TestRunnerWKWebView *webView, long x, long y)
163 {
164     CGPoint point = CGPointMake(x, y);
165     point = [webView _convertPointFromContentsToView:point];
166     point = [webView convertPoint:point toView:nil];
167     point = [webView.window convertPoint:point toWindow:nil];
168     return point;
169 }
170
171 void UIScriptController::touchDownAtPoint(long x, long y, long touchCount, JSValueRef callback)
172 {
173     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
174
175     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
176     [[HIDEventGenerator sharedHIDEventGenerator] touchDown:location touchCount:touchCount completionBlock:^{
177         if (!m_context)
178             return;
179         m_context->asyncTaskComplete(callbackID);
180     }];
181 }
182
183 void UIScriptController::liftUpAtPoint(long x, long y, long touchCount, JSValueRef callback)
184 {
185     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
186     
187     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
188     [[HIDEventGenerator sharedHIDEventGenerator] liftUp:location touchCount:touchCount completionBlock:^{
189         if (!m_context)
190             return;
191         m_context->asyncTaskComplete(callbackID);
192     }];
193 }
194
195 void UIScriptController::singleTapAtPoint(long x, long y, JSValueRef callback)
196 {
197     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
198
199     [[HIDEventGenerator sharedHIDEventGenerator] tap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
200         if (!m_context)
201             return;
202         m_context->asyncTaskComplete(callbackID);
203     }];
204 }
205
206 void UIScriptController::doubleTapAtPoint(long x, long y, JSValueRef callback)
207 {
208     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
209
210     [[HIDEventGenerator sharedHIDEventGenerator] doubleTap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
211         if (!m_context)
212             return;
213         m_context->asyncTaskComplete(callbackID);
214     }];
215 }
216
217 void UIScriptController::stylusDownAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
218 {
219     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
220
221     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
222     [[HIDEventGenerator sharedHIDEventGenerator] stylusDownAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
223         if (!m_context)
224             return;
225         m_context->asyncTaskComplete(callbackID);
226     }];
227 }
228
229 void UIScriptController::stylusMoveToPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
230 {
231     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
232
233     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
234     [[HIDEventGenerator sharedHIDEventGenerator] stylusMoveToPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
235         if (!m_context)
236             return;
237         m_context->asyncTaskComplete(callbackID);
238     }];
239 }
240
241 void UIScriptController::stylusUpAtPoint(long x, long y, JSValueRef callback)
242 {
243     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
244
245     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
246     [[HIDEventGenerator sharedHIDEventGenerator] stylusUpAtPoint:location completionBlock:^{
247         if (!m_context)
248             return;
249         m_context->asyncTaskComplete(callbackID);
250     }];
251 }
252
253 void UIScriptController::stylusTapAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
254 {
255     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
256
257     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
258     [[HIDEventGenerator sharedHIDEventGenerator] stylusTapAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
259         if (!m_context)
260             return;
261         m_context->asyncTaskComplete(callbackID);
262     }];
263 }
264     
265 void convertCoordinates(NSMutableDictionary *event)
266 {
267     if (event[HIDEventTouchesKey]) {
268         for (NSMutableDictionary *touch in event[HIDEventTouchesKey]) {
269             auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), (long)[touch[HIDEventXKey] doubleValue], (long)[touch[HIDEventYKey]doubleValue]);
270             touch[HIDEventXKey] = @(location.x);
271             touch[HIDEventYKey] = @(location.y);
272         }
273     }
274 }
275
276 void UIScriptController::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
277 {
278     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
279
280     String jsonString = eventsJSON->string();
281     auto eventInfo = dynamic_objc_cast<NSDictionary>([NSJSONSerialization JSONObjectWithData:[(NSString *)jsonString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves error:nil]);
282     
283     for (NSMutableDictionary *event in eventInfo[TopLevelEventInfoKey]) {
284         if (![event[HIDEventCoordinateSpaceKey] isEqualToString:HIDEventCoordinateSpaceTypeContent])
285             continue;
286         
287         if (event[HIDEventStartEventKey])
288             convertCoordinates(event[HIDEventStartEventKey]);
289         
290         if (event[HIDEventEndEventKey])
291             convertCoordinates(event[HIDEventEndEventKey]);
292         
293         if (event[HIDEventTouchesKey])
294             convertCoordinates(event);
295     }
296     
297     if (!eventInfo || ![eventInfo isKindOfClass:[NSDictionary class]]) {
298         WTFLogAlways("JSON is not convertible to a dictionary");
299         return;
300     }
301     
302     [[HIDEventGenerator sharedHIDEventGenerator] sendEventStream:eventInfo completionBlock:^{
303         if (!m_context)
304             return;
305         m_context->asyncTaskComplete(callbackID);
306     }];
307 }
308
309 void UIScriptController::dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, JSValueRef callback)
310 {
311     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
312
313     CGPoint startPoint = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), startX, startY);
314     CGPoint endPoint = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), endX, endY);
315     
316     [[HIDEventGenerator sharedHIDEventGenerator] dragWithStartPoint:startPoint endPoint:endPoint duration:durationSeconds completionBlock:^{
317         if (!m_context)
318             return;
319         m_context->asyncTaskComplete(callbackID);
320     }];
321 }
322     
323 void UIScriptController::longPressAtPoint(long x, long y, JSValueRef callback)
324 {
325     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
326     
327     [[HIDEventGenerator sharedHIDEventGenerator] longPress:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
328         if (!m_context)
329             return;
330         m_context->asyncTaskComplete(callbackID);
331     }];
332 }
333
334 void UIScriptController::enterText(JSStringRef text)
335 {
336     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
337     auto textAsCFString = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, text));
338     [webView _simulateTextEntered:(NSString *)textAsCFString.get()];
339 }
340
341 void UIScriptController::typeCharacterUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
342 {
343     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
344
345     // Assumes that the keyboard is already shown.
346     [[HIDEventGenerator sharedHIDEventGenerator] keyPress:toWTFString(toWK(character)) completionBlock:^{
347         if (!m_context)
348             return;
349         m_context->asyncTaskComplete(callbackID);
350     }];
351 }
352
353 void UIScriptController::keyDownUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
354 {
355     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
356
357     // Assumes that the keyboard is already shown.
358     [[HIDEventGenerator sharedHIDEventGenerator] keyDown:toWTFString(toWK(character)) completionBlock:^{
359         if (!m_context)
360             return;
361         m_context->asyncTaskComplete(callbackID);
362     }];
363 }
364
365 void UIScriptController::keyUpUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
366 {
367     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
368
369     // Assumes that the keyboard is already shown.
370     [[HIDEventGenerator sharedHIDEventGenerator] keyUp:toWTFString(toWK(character)) completionBlock:^{
371         if (!m_context)
372             return;
373         m_context->asyncTaskComplete(callbackID);
374     }];
375 }
376
377 void UIScriptController::dismissFormAccessoryView()
378 {
379     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
380     [webView dismissFormAccessoryView];
381 }
382
383 JSRetainPtr<JSStringRef> UIScriptController::selectFormPopoverTitle() const
384 {
385     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
386     return JSStringCreateWithCFString((CFStringRef)webView.selectFormPopoverTitle);
387 }
388
389 JSRetainPtr<JSStringRef> UIScriptController::textContentType() const
390 {
391     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
392     return JSStringCreateWithCFString((CFStringRef)(webView.textContentTypeForTesting ?: @""));
393 }
394
395 JSRetainPtr<JSStringRef> UIScriptController::formInputLabel() const
396 {
397     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
398     return JSStringCreateWithCFString((CFStringRef)webView.formInputLabel);
399 }
400
401 void UIScriptController::selectFormAccessoryPickerRow(long rowIndex)
402 {
403     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
404     [webView selectFormAccessoryPickerRow:rowIndex];
405 }
406
407 void UIScriptController::setTimePickerValue(long hour, long minute)
408 {
409     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
410     [webView setTimePickerValueToHour:hour minute:minute];
411 }
412
413 void UIScriptController::setShareSheetCompletesImmediatelyWithResolution(bool resolved)
414 {
415     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
416     [webView _setShareSheetCompletesImmediatelyWithResolutionForTesting:resolved];
417 }
418     
419 JSObjectRef UIScriptController::contentsOfUserInterfaceItem(JSStringRef interfaceItem) const
420 {
421     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
422     NSDictionary *contentDictionary = [webView _contentsOfUserInterfaceItem:toWTFString(toWK(interfaceItem))];
423     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:contentDictionary inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
424 }
425
426 static CGPoint contentOffsetBoundedInValidRange(UIScrollView *scrollView, CGPoint contentOffset)
427 {
428     UIEdgeInsets contentInsets = scrollView.contentInset;
429     CGSize contentSize = scrollView.contentSize;
430     CGSize scrollViewSize = scrollView.bounds.size;
431
432     CGFloat maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width;
433     contentOffset.x = std::min(maxHorizontalOffset, contentOffset.x);
434     contentOffset.x = std::max(-contentInsets.left, contentOffset.x);
435
436     CGFloat maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height;
437     contentOffset.y = std::min(maxVerticalOffset, contentOffset.y);
438     contentOffset.y = std::max(-contentInsets.top, contentOffset.y);
439     return contentOffset;
440 }
441
442 void UIScriptController::scrollToOffset(long x, long y)
443 {
444     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
445     [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:YES];
446 }
447
448 void UIScriptController::immediateScrollToOffset(long x, long y)
449 {
450     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
451     [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:NO];
452 }
453
454 void UIScriptController::immediateZoomToScale(double scale)
455 {
456     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
457     [webView.scrollView setZoomScale:scale animated:NO];
458 }
459
460 void UIScriptController::keyboardAccessoryBarNext()
461 {
462     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
463     [webView keyboardAccessoryBarNext];
464 }
465
466 void UIScriptController::keyboardAccessoryBarPrevious()
467 {
468     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
469     [webView keyboardAccessoryBarPrevious];
470 }
471
472 void UIScriptController::applyAutocorrection(JSStringRef newString, JSStringRef oldString, JSValueRef callback)
473 {
474     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
475
476     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
477     [webView applyAutocorrection:toWTFString(toWK(newString)) toString:toWTFString(toWK(oldString)) withCompletionHandler:^ {
478         // applyAutocorrection can call its completion handler synchronously,
479         // which makes UIScriptController unhappy (see bug 172884).
480         dispatch_async(dispatch_get_main_queue(), ^ {
481             if (!m_context)
482                 return;
483             m_context->asyncTaskComplete(callbackID);
484         });
485     }];
486 }
487
488 double UIScriptController::minimumZoomScale() const
489 {
490     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
491     return webView.scrollView.minimumZoomScale;
492 }
493
494 double UIScriptController::maximumZoomScale() const
495 {
496     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
497     return webView.scrollView.maximumZoomScale;
498 }
499
500 std::optional<bool> UIScriptController::stableStateOverride() const
501 {
502     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
503     if (webView._stableStateOverride)
504         return webView._stableStateOverride.boolValue;
505
506     return std::nullopt;
507 }
508
509 void UIScriptController::setStableStateOverride(std::optional<bool> overrideValue)
510 {
511     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
512     if (overrideValue)
513         webView._stableStateOverride = @(overrideValue.value());
514     else
515         webView._stableStateOverride = nil;
516 }
517
518 JSObjectRef UIScriptController::contentVisibleRect() const
519 {
520     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
521
522     CGRect contentVisibleRect = webView._contentVisibleRect;
523     
524     WebCore::FloatRect rect(contentVisibleRect.origin.x, contentVisibleRect.origin.y, contentVisibleRect.size.width, contentVisibleRect.size.height);
525     return m_context->objectFromRect(rect);
526 }
527
528 JSObjectRef UIScriptController::selectionRangeViewRects() const
529 {
530     NSMutableArray *selectionRects = [[NSMutableArray alloc] init];
531     NSArray *rects = TestController::singleton().mainWebView()->platformView()._uiTextSelectionRects;
532     for (NSValue *rect in rects)
533         [selectionRects addObject:toNSDictionary([rect CGRectValue])];
534
535     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:selectionRects inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
536 }
537
538 JSObjectRef UIScriptController::textSelectionCaretRect() const
539 {
540     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(TestController::singleton().mainWebView()->platformView()._uiTextCaretRect) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
541 }
542
543 JSObjectRef UIScriptController::selectionStartGrabberViewRect() const
544 {
545     WKWebView *webView = TestController::singleton().mainWebView()->platformView();
546     UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
547     UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
548     auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"startGrabber"] frame] toView:contentView];
549     auto jsContext = m_context->jsContext();
550     return JSValueToObject(jsContext, [JSValue valueWithObject:toNSDictionary(frameInContentCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:jsContext]].JSValueRef, nullptr);
551 }
552
553 JSObjectRef UIScriptController::selectionEndGrabberViewRect() const
554 {
555     WKWebView *webView = TestController::singleton().mainWebView()->platformView();
556     UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
557     UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
558     auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"endGrabber"] frame] toView:contentView];
559     auto jsContext = m_context->jsContext();
560     return JSValueToObject(jsContext, [JSValue valueWithObject:toNSDictionary(frameInContentCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:jsContext]].JSValueRef, nullptr);
561 }
562
563 JSObjectRef UIScriptController::inputViewBounds() const
564 {
565     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(TestController::singleton().mainWebView()->platformView()._inputViewBounds) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
566 }
567
568 void UIScriptController::removeAllDynamicDictionaries()
569 {
570     [UIKeyboard removeAllDynamicDictionaries];
571 }
572
573 JSRetainPtr<JSStringRef> UIScriptController::scrollingTreeAsText() const
574 {
575     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
576     return JSStringCreateWithCFString((CFStringRef)[webView _scrollingTreeAsText]);
577 }
578
579 JSObjectRef UIScriptController::propertiesOfLayerWithID(uint64_t layerID) const
580 {
581     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:[TestController::singleton().mainWebView()->platformView() _propertiesOfLayerWithID:layerID] inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
582 }
583
584 static UIDeviceOrientation toUIDeviceOrientation(DeviceOrientation* orientation)
585 {
586     if (!orientation)
587         return UIDeviceOrientationPortrait;
588         
589     switch (*orientation) {
590     case DeviceOrientation::Portrait:
591         return UIDeviceOrientationPortrait;
592     case DeviceOrientation::PortraitUpsideDown:
593         return UIDeviceOrientationPortraitUpsideDown;
594     case DeviceOrientation::LandscapeLeft:
595         return UIDeviceOrientationLandscapeLeft;
596     case DeviceOrientation::LandscapeRight:
597         return UIDeviceOrientationLandscapeRight;
598     }
599     
600     return UIDeviceOrientationPortrait;
601 }
602
603 void UIScriptController::simulateRotation(DeviceOrientation* orientation, JSValueRef callback)
604 {
605     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
606     webView.usesSafariLikeRotation = NO;
607     
608     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
609     
610     webView.rotationDidEndCallback = ^{
611         if (!m_context)
612             return;
613         m_context->asyncTaskComplete(callbackID);
614     };
615     
616     [[UIDevice currentDevice] setOrientation:toUIDeviceOrientation(orientation) animated:YES];
617 }
618
619 void UIScriptController::simulateRotationLikeSafari(DeviceOrientation* orientation, JSValueRef callback)
620 {
621     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
622     webView.usesSafariLikeRotation = YES;
623     
624     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
625     
626     webView.rotationDidEndCallback = ^{
627         if (!m_context)
628             return;
629         m_context->asyncTaskComplete(callbackID);
630     };
631     
632     [[UIDevice currentDevice] setOrientation:toUIDeviceOrientation(orientation) animated:YES];
633 }
634
635 void UIScriptController::findString(JSStringRef string, unsigned long options, unsigned long maxCount)
636 {
637     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
638     [webView _findString:toWTFString(toWK(string)) options:options maxCount:maxCount];
639 }
640
641 void UIScriptController::removeViewFromWindow(JSValueRef callback)
642 {
643     TestController::singleton().mainWebView()->removeFromWindow();
644 }
645
646 void UIScriptController::addViewToWindow(JSValueRef callback)
647 {
648     TestController::singleton().mainWebView()->addToWindow();
649 }
650
651 void UIScriptController::platformSetDidStartFormControlInteractionCallback()
652 {
653     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
654     webView.didStartFormControlInteractionCallback = ^{
655         if (!m_context)
656             return;
657         m_context->fireCallback(CallbackTypeDidStartFormControlInteraction);
658     };
659 }
660
661 void UIScriptController::platformSetDidEndFormControlInteractionCallback()
662 {
663     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
664     webView.didEndFormControlInteractionCallback = ^{
665         if (!m_context)
666             return;
667         m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
668     };
669 }
670     
671 void UIScriptController::platformSetDidShowForcePressPreviewCallback()
672 {
673     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
674     webView.didShowForcePressPreviewCallback = ^ {
675         if (!m_context)
676             return;
677         m_context->fireCallback(CallbackTypeDidShowForcePressPreview);
678     };
679 }
680
681 void UIScriptController::platformSetDidDismissForcePressPreviewCallback()
682 {
683     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
684     webView.didDismissForcePressPreviewCallback = ^ {
685         if (!m_context)
686             return;
687         m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
688     };
689 }
690
691 void UIScriptController::platformSetWillBeginZoomingCallback()
692 {
693     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
694     webView.willBeginZoomingCallback = ^{
695         if (!m_context)
696             return;
697         m_context->fireCallback(CallbackTypeWillBeginZooming);
698     };
699 }
700
701 void UIScriptController::platformSetDidEndZoomingCallback()
702 {
703     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
704     webView.didEndZoomingCallback = ^{
705         if (!m_context)
706             return;
707         m_context->fireCallback(CallbackTypeDidEndZooming);
708     };
709 }
710
711 void UIScriptController::platformSetDidShowKeyboardCallback()
712 {
713     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
714     webView.didShowKeyboardCallback = ^{
715         if (!m_context)
716             return;
717         m_context->fireCallback(CallbackTypeDidShowKeyboard);
718     };
719 }
720
721 void UIScriptController::platformSetDidHideKeyboardCallback()
722 {
723     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
724     webView.didHideKeyboardCallback = ^{
725         if (!m_context)
726             return;
727         m_context->fireCallback(CallbackTypeDidHideKeyboard);
728     };
729 }
730
731 void UIScriptController::platformSetDidEndScrollingCallback()
732 {
733     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
734     webView.didEndScrollingCallback = ^{
735         if (!m_context)
736             return;
737         m_context->fireCallback(CallbackTypeDidEndScrolling);
738     };
739 }
740
741 void UIScriptController::platformClearAllCallbacks()
742 {
743     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
744     
745     webView.didStartFormControlInteractionCallback = nil;
746     webView.didEndFormControlInteractionCallback = nil;
747     webView.didShowForcePressPreviewCallback = nil;
748     webView.didDismissForcePressPreviewCallback = nil;
749     webView.didEndZoomingCallback = nil;
750     webView.willBeginZoomingCallback = nil;
751     webView.didHideKeyboardCallback = nil;
752     webView.didShowKeyboardCallback = nil;
753     webView.didEndScrollingCallback = nil;
754     webView.rotationDidEndCallback = nil;
755 }
756
757 void UIScriptController::setSafeAreaInsets(double top, double right, double bottom, double left)
758 {
759     UIEdgeInsets insets = UIEdgeInsetsMake(top, left, bottom, right);
760     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
761     webView.overrideSafeAreaInsets = insets;
762 }
763
764 void UIScriptController::beginBackSwipe(JSValueRef callback)
765 {
766     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
767     [webView _beginBackSwipeForTesting];
768
769     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
770     m_context->asyncTaskComplete(callbackID);
771 }
772
773 void UIScriptController::completeBackSwipe(JSValueRef callback)
774 {
775     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
776     [webView _completeBackSwipeForTesting];
777
778     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
779     m_context->asyncTaskComplete(callbackID);
780 }
781
782 }
783
784 #endif // PLATFORM(IOS)