ce67f5e5439f87c75d9ca65e2464cfeff6a38dd6
[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 "UIKitSPI.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::typeCharacterUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
335 {
336     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
337
338     // Assumes that the keyboard is already shown.
339     [[HIDEventGenerator sharedHIDEventGenerator] keyPress:toWTFString(toWK(character)) completionBlock:^{
340         if (!m_context)
341             return;
342         m_context->asyncTaskComplete(callbackID);
343     }];
344 }
345
346 void UIScriptController::keyDownUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
347 {
348     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
349
350     // Assumes that the keyboard is already shown.
351     [[HIDEventGenerator sharedHIDEventGenerator] keyDown:toWTFString(toWK(character)) completionBlock:^{
352         if (!m_context)
353             return;
354         m_context->asyncTaskComplete(callbackID);
355     }];
356 }
357
358 void UIScriptController::keyUpUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
359 {
360     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
361
362     // Assumes that the keyboard is already shown.
363     [[HIDEventGenerator sharedHIDEventGenerator] keyUp:toWTFString(toWK(character)) completionBlock:^{
364         if (!m_context)
365             return;
366         m_context->asyncTaskComplete(callbackID);
367     }];
368 }
369
370 void UIScriptController::selectTextCandidateAtIndex(long index, JSValueRef callback)
371 {
372 #if USE(APPLE_INTERNAL_SDK)
373     static const float textPredictionsPollingInterval = 0.1;
374     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
375     waitForTextPredictionsViewAndSelectCandidateAtIndex(index, callbackID, textPredictionsPollingInterval);
376 #else
377     // FIXME: This is a no-op on non-internal builds due to UIKeyboardPredictionView being unavailable. Ideally, there should be a better way to
378     // retrieve information and interact with the predictive text view that will be compatible with OpenSource.
379     UNUSED_PARAM(index);
380     UNUSED_PARAM(callback);
381 #endif
382 }
383
384 void UIScriptController::waitForTextPredictionsViewAndSelectCandidateAtIndex(long index, unsigned callbackID, float interval)
385 {
386     id UIKeyboardPredictionViewClass = NSClassFromString(@"UIKeyboardPredictionView");
387     if (!UIKeyboardPredictionViewClass)
388         return;
389
390 #if USE(APPLE_INTERNAL_SDK)
391     UIKeyboardPredictionView *predictionView = (UIKeyboardPredictionView *)[UIKeyboardPredictionViewClass activeInstance];
392     if (![predictionView hasPredictions]) {
393         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^() {
394             waitForTextPredictionsViewAndSelectCandidateAtIndex(index, callbackID, interval);
395         });
396         return;
397     }
398
399     PlatformWKView webView = TestController::singleton().mainWebView()->platformView();
400     CGRect predictionViewFrame = [predictionView frame];
401     // This assumes there are 3 predicted text cells of equal width, which is the case on iOS.
402     float offsetX = (index * 2 + 1) * CGRectGetWidth(predictionViewFrame) / 6;
403     float offsetY = CGRectGetHeight(webView.window.frame) - CGRectGetHeight([[predictionView superview] frame]) + CGRectGetHeight(predictionViewFrame) / 2;
404     [[HIDEventGenerator sharedHIDEventGenerator] tap:CGPointMake(offsetX, offsetY) completionBlock:^{
405         if (m_context)
406             m_context->asyncTaskComplete(callbackID);
407     }];
408 #else
409     UNUSED_PARAM(index);
410     UNUSED_PARAM(callbackID);
411     UNUSED_PARAM(interval);
412 #endif
413 }
414
415 void UIScriptController::dismissFormAccessoryView()
416 {
417     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
418     [webView dismissFormAccessoryView];
419 }
420
421 void UIScriptController::selectFormAccessoryPickerRow(long rowIndex)
422 {
423     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
424     [webView selectFormAccessoryPickerRow:rowIndex];
425 }
426     
427 JSObjectRef UIScriptController::contentsOfUserInterfaceItem(JSStringRef interfaceItem) const
428 {
429     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
430     NSDictionary *contentDictionary = [webView _contentsOfUserInterfaceItem:toWTFString(toWK(interfaceItem))];
431     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:contentDictionary inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
432 }
433
434 static CGPoint contentOffsetBoundedInValidRange(UIScrollView *scrollView, CGPoint contentOffset)
435 {
436     UIEdgeInsets contentInsets = scrollView.contentInset;
437     CGSize contentSize = scrollView.contentSize;
438     CGSize scrollViewSize = scrollView.bounds.size;
439
440     CGFloat maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width;
441     contentOffset.x = std::min(maxHorizontalOffset, contentOffset.x);
442     contentOffset.x = std::max(-contentInsets.left, contentOffset.x);
443
444     CGFloat maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height;
445     contentOffset.y = std::min(maxVerticalOffset, contentOffset.y);
446     contentOffset.y = std::max(-contentInsets.top, contentOffset.y);
447     return contentOffset;
448 }
449
450 void UIScriptController::scrollToOffset(long x, long y)
451 {
452     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
453     [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:YES];
454 }
455
456 void UIScriptController::immediateScrollToOffset(long x, long y)
457 {
458     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
459     [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:NO];
460 }
461
462 void UIScriptController::immediateZoomToScale(double scale)
463 {
464     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
465     [webView.scrollView setZoomScale:scale animated:NO];
466 }
467
468 void UIScriptController::keyboardAccessoryBarNext()
469 {
470     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
471     [webView keyboardAccessoryBarNext];
472 }
473
474 void UIScriptController::keyboardAccessoryBarPrevious()
475 {
476     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
477     [webView keyboardAccessoryBarPrevious];
478 }
479
480 double UIScriptController::minimumZoomScale() const
481 {
482     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
483     return webView.scrollView.minimumZoomScale;
484 }
485
486 double UIScriptController::maximumZoomScale() const
487 {
488     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
489     return webView.scrollView.maximumZoomScale;
490 }
491
492 std::optional<bool> UIScriptController::stableStateOverride() const
493 {
494     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
495     if (webView._stableStateOverride)
496         return webView._stableStateOverride.boolValue;
497
498     return std::nullopt;
499 }
500
501 void UIScriptController::setStableStateOverride(std::optional<bool> overrideValue)
502 {
503     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
504     if (overrideValue)
505         webView._stableStateOverride = @(overrideValue.value());
506     else
507         webView._stableStateOverride = nil;
508 }
509
510 JSObjectRef UIScriptController::contentVisibleRect() const
511 {
512     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
513
514     CGRect contentVisibleRect = webView._contentVisibleRect;
515     
516     WebCore::FloatRect rect(contentVisibleRect.origin.x, contentVisibleRect.origin.y, contentVisibleRect.size.width, contentVisibleRect.size.height);
517     return m_context->objectFromRect(rect);
518 }
519
520 JSObjectRef UIScriptController::selectionRangeViewRects() const
521 {
522     NSMutableArray *selectionRects = [[NSMutableArray alloc] init];
523     NSArray *rects = TestController::singleton().mainWebView()->platformView()._uiTextSelectionRects;
524     for (NSValue *rect in rects)
525         [selectionRects addObject:toNSDictionary([rect CGRectValue])];
526
527     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:selectionRects inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
528 }
529
530 JSObjectRef UIScriptController::textSelectionCaretRect() const
531 {
532     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(TestController::singleton().mainWebView()->platformView()._uiTextCaretRect) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
533 }
534
535 JSObjectRef UIScriptController::inputViewBounds() const
536 {
537     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(TestController::singleton().mainWebView()->platformView()._inputViewBounds) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
538 }
539
540 void UIScriptController::removeAllDynamicDictionaries()
541 {
542     [UIKeyboard removeAllDynamicDictionaries];
543 }
544
545 JSRetainPtr<JSStringRef> UIScriptController::scrollingTreeAsText() const
546 {
547     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
548     return JSStringCreateWithCFString((CFStringRef)[webView _scrollingTreeAsText]);
549 }
550
551 JSObjectRef UIScriptController::propertiesOfLayerWithID(uint64_t layerID) const
552 {
553     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:[TestController::singleton().mainWebView()->platformView() _propertiesOfLayerWithID:layerID] inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
554 }
555
556 void UIScriptController::removeViewFromWindow(JSValueRef callback)
557 {
558     TestController::singleton().mainWebView()->removeFromWindow();
559 }
560
561 void UIScriptController::addViewToWindow(JSValueRef callback)
562 {
563     TestController::singleton().mainWebView()->addToWindow();
564 }
565
566 void UIScriptController::platformSetDidStartFormControlInteractionCallback()
567 {
568     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
569     webView.didStartFormControlInteractionCallback = ^{
570         if (!m_context)
571             return;
572         m_context->fireCallback(CallbackTypeDidStartFormControlInteraction);
573     };
574 }
575
576 void UIScriptController::platformSetDidEndFormControlInteractionCallback()
577 {
578     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
579     webView.didEndFormControlInteractionCallback = ^{
580         if (!m_context)
581             return;
582         m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
583     };
584 }
585     
586 void UIScriptController::platformSetDidShowForcePressPreviewCallback()
587 {
588     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
589     webView.didShowForcePressPreviewCallback = ^ {
590         if (!m_context)
591             return;
592         m_context->fireCallback(CallbackTypeDidShowForcePressPreview);
593     };
594 }
595
596 void UIScriptController::platformSetDidDismissForcePressPreviewCallback()
597 {
598     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
599     webView.didDismissForcePressPreviewCallback = ^ {
600         if (!m_context)
601             return;
602         m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
603     };
604 }
605
606 void UIScriptController::platformSetWillBeginZoomingCallback()
607 {
608     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
609     webView.willBeginZoomingCallback = ^{
610         if (!m_context)
611             return;
612         m_context->fireCallback(CallbackTypeWillBeginZooming);
613     };
614 }
615
616 void UIScriptController::platformSetDidEndZoomingCallback()
617 {
618     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
619     webView.didEndZoomingCallback = ^{
620         if (!m_context)
621             return;
622         m_context->fireCallback(CallbackTypeDidEndZooming);
623     };
624 }
625
626 void UIScriptController::platformSetDidShowKeyboardCallback()
627 {
628     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
629     webView.didShowKeyboardCallback = ^{
630         if (!m_context)
631             return;
632         m_context->fireCallback(CallbackTypeDidShowKeyboard);
633     };
634 }
635
636 void UIScriptController::platformSetDidHideKeyboardCallback()
637 {
638     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
639     webView.didHideKeyboardCallback = ^{
640         if (!m_context)
641             return;
642         m_context->fireCallback(CallbackTypeDidHideKeyboard);
643     };
644 }
645
646 void UIScriptController::platformSetDidEndScrollingCallback()
647 {
648     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
649     webView.didEndScrollingCallback = ^{
650         if (!m_context)
651             return;
652         m_context->fireCallback(CallbackTypeDidEndScrolling);
653     };
654 }
655
656 void UIScriptController::platformClearAllCallbacks()
657 {
658     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
659     
660     webView.didStartFormControlInteractionCallback = nil;
661     webView.didEndFormControlInteractionCallback = nil;
662     webView.didShowForcePressPreviewCallback = nil;
663     webView.didDismissForcePressPreviewCallback = nil;
664     webView.didEndZoomingCallback = nil;
665     webView.willBeginZoomingCallback = nil;
666     webView.didHideKeyboardCallback = nil;
667     webView.didShowKeyboardCallback = nil;
668     webView.didEndScrollingCallback = nil;
669 }
670
671 }
672
673 #endif // PLATFORM(IOS)