ffea7a00fae0d38bc3e4fa4ebc54d2ec568b2d2a
[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_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
46 namespace WTR {
47
48 static NSDictionary *toNSDictionary(CGRect rect)
49 {
50     return @{
51         @"left": @(rect.origin.x),
52         @"top": @(rect.origin.y),
53         @"width": @(rect.size.width),
54         @"height": @(rect.size.height)
55     };
56 }
57     
58 void UIScriptController::checkForOutstandingCallbacks()
59 {
60     if (![[HIDEventGenerator sharedHIDEventGenerator] checkForOutstandingCallbacks])
61         [NSException raise:@"WebKitTestRunnerTestProblem" format:@"The test completed before all synthesized events had been handled. Perhaps you're calling notifyDone() too early?"];
62 }
63
64 void UIScriptController::doAfterPresentationUpdate(JSValueRef callback)
65 {
66     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
67
68     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
69     [webView _doAfterNextPresentationUpdate:^{
70         if (!m_context)
71             return;
72         m_context->asyncTaskComplete(callbackID);
73     }];
74 }
75
76 void UIScriptController::doAfterNextStablePresentationUpdate(JSValueRef callback)
77 {
78     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
79
80     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
81     [webView _doAfterNextStablePresentationUpdate:^() {
82         if (m_context)
83             m_context->asyncTaskComplete(callbackID);
84     }];
85 }
86
87 void UIScriptController::doAfterVisibleContentRectUpdate(JSValueRef callback)
88 {
89     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
90
91     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
92     [webView _doAfterNextVisibleContentRectUpdate:^ {
93         if (!m_context)
94             return;
95         m_context->asyncTaskComplete(callbackID);
96     }];
97 }
98
99 void UIScriptController::zoomToScale(double scale, JSValueRef callback)
100 {
101     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
102
103     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
104
105     [webView zoomToScale:scale animated:YES completionHandler:^{
106         if (!m_context)
107             return;
108         m_context->asyncTaskComplete(callbackID);
109     }];
110 }
111
112 void UIScriptController::retrieveSpeakSelectionContent(JSValueRef callback)
113 {
114     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
115     
116     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
117     
118     [webView accessibilityRetrieveSpeakSelectionContentWithCompletionHandler:^() {
119         if (!m_context)
120             return;
121         m_context->asyncTaskComplete(callbackID);
122     }];
123 }
124
125 JSRetainPtr<JSStringRef> UIScriptController::accessibilitySpeakSelectionContent() const
126 {
127     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
128     return JSStringCreateWithCFString((CFStringRef)webView.accessibilitySpeakSelectionContent);
129 }
130
131 void UIScriptController::simulateAccessibilitySettingsChangeNotification(JSValueRef callback)
132 {
133     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
134
135     auto* webView = TestController::singleton().mainWebView()->platformView();
136     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
137     [center postNotificationName:UIAccessibilityInvertColorsStatusDidChangeNotification object:webView];
138
139     [webView _doAfterNextPresentationUpdate: ^{
140         if (!m_context)
141             return;
142         m_context->asyncTaskComplete(callbackID);
143     }];
144 }
145
146 double UIScriptController::zoomScale() const
147 {
148     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
149     return webView.scrollView.zoomScale;
150 }
151
152 static CGPoint globalToContentCoordinates(TestRunnerWKWebView *webView, long x, long y)
153 {
154     CGPoint point = CGPointMake(x, y);
155     point = [webView _convertPointFromContentsToView:point];
156     point = [webView convertPoint:point toView:nil];
157     point = [webView.window convertPoint:point toWindow:nil];
158     return point;
159 }
160
161 void UIScriptController::touchDownAtPoint(long x, long y, long touchCount, JSValueRef callback)
162 {
163     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
164
165     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
166     [[HIDEventGenerator sharedHIDEventGenerator] touchDown:location touchCount:touchCount completionBlock:^{
167         if (!m_context)
168             return;
169         m_context->asyncTaskComplete(callbackID);
170     }];
171 }
172
173 void UIScriptController::liftUpAtPoint(long x, long y, long touchCount, JSValueRef callback)
174 {
175     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
176     
177     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
178     [[HIDEventGenerator sharedHIDEventGenerator] liftUp:location touchCount:touchCount completionBlock:^{
179         if (!m_context)
180             return;
181         m_context->asyncTaskComplete(callbackID);
182     }];
183 }
184
185 void UIScriptController::singleTapAtPoint(long x, long y, JSValueRef callback)
186 {
187     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
188
189     [[HIDEventGenerator sharedHIDEventGenerator] tap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
190         if (!m_context)
191             return;
192         m_context->asyncTaskComplete(callbackID);
193     }];
194 }
195
196 void UIScriptController::doubleTapAtPoint(long x, long y, JSValueRef callback)
197 {
198     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
199
200     [[HIDEventGenerator sharedHIDEventGenerator] doubleTap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
201         if (!m_context)
202             return;
203         m_context->asyncTaskComplete(callbackID);
204     }];
205 }
206
207 void UIScriptController::stylusDownAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
208 {
209     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
210
211     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
212     [[HIDEventGenerator sharedHIDEventGenerator] stylusDownAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
213         if (!m_context)
214             return;
215         m_context->asyncTaskComplete(callbackID);
216     }];
217 }
218
219 void UIScriptController::stylusMoveToPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
220 {
221     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
222
223     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
224     [[HIDEventGenerator sharedHIDEventGenerator] stylusMoveToPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
225         if (!m_context)
226             return;
227         m_context->asyncTaskComplete(callbackID);
228     }];
229 }
230
231 void UIScriptController::stylusUpAtPoint(long x, long y, JSValueRef callback)
232 {
233     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
234
235     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
236     [[HIDEventGenerator sharedHIDEventGenerator] stylusUpAtPoint:location completionBlock:^{
237         if (!m_context)
238             return;
239         m_context->asyncTaskComplete(callbackID);
240     }];
241 }
242
243 void UIScriptController::stylusTapAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
244 {
245     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
246
247     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
248     [[HIDEventGenerator sharedHIDEventGenerator] stylusTapAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
249         if (!m_context)
250             return;
251         m_context->asyncTaskComplete(callbackID);
252     }];
253 }
254     
255 void convertCoordinates(NSMutableDictionary *event)
256 {
257     if (event[HIDEventTouchesKey]) {
258         for (NSMutableDictionary *touch in event[HIDEventTouchesKey]) {
259             auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), (long)[touch[HIDEventXKey] doubleValue], (long)[touch[HIDEventYKey]doubleValue]);
260             touch[HIDEventXKey] = @(location.x);
261             touch[HIDEventYKey] = @(location.y);
262         }
263     }
264 }
265
266 void UIScriptController::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
267 {
268     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
269
270     String jsonString = eventsJSON->string();
271     auto eventInfo = dynamic_objc_cast<NSDictionary>([NSJSONSerialization JSONObjectWithData:[(NSString *)jsonString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves error:nil]);
272     
273     for (NSMutableDictionary *event in eventInfo[TopLevelEventInfoKey]) {
274         if (![event[HIDEventCoordinateSpaceKey] isEqualToString:HIDEventCoordinateSpaceTypeContent])
275             continue;
276         
277         if (event[HIDEventStartEventKey])
278             convertCoordinates(event[HIDEventStartEventKey]);
279         
280         if (event[HIDEventEndEventKey])
281             convertCoordinates(event[HIDEventEndEventKey]);
282         
283         if (event[HIDEventTouchesKey])
284             convertCoordinates(event);
285     }
286     
287     if (!eventInfo || ![eventInfo isKindOfClass:[NSDictionary class]]) {
288         WTFLogAlways("JSON is not convertible to a dictionary");
289         return;
290     }
291     
292     [[HIDEventGenerator sharedHIDEventGenerator] sendEventStream:eventInfo completionBlock:^{
293         if (!m_context)
294             return;
295         m_context->asyncTaskComplete(callbackID);
296     }];
297 }
298
299 void UIScriptController::dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, JSValueRef callback)
300 {
301     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
302
303     CGPoint startPoint = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), startX, startY);
304     CGPoint endPoint = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), endX, endY);
305     
306     [[HIDEventGenerator sharedHIDEventGenerator] dragWithStartPoint:startPoint endPoint:endPoint duration:durationSeconds completionBlock:^{
307         if (!m_context)
308             return;
309         m_context->asyncTaskComplete(callbackID);
310     }];
311 }
312     
313 void UIScriptController::longPressAtPoint(long x, long y, JSValueRef callback)
314 {
315     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
316     
317     [[HIDEventGenerator sharedHIDEventGenerator] longPress:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
318         if (!m_context)
319             return;
320         m_context->asyncTaskComplete(callbackID);
321     }];
322 }
323
324 void UIScriptController::enterText(JSStringRef text)
325 {
326     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
327     auto textAsCFString = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, text));
328     [webView _simulateTextEntered:(NSString *)textAsCFString.get()];
329 }
330
331 void UIScriptController::typeCharacterUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
332 {
333     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
334
335     // Assumes that the keyboard is already shown.
336     [[HIDEventGenerator sharedHIDEventGenerator] keyPress:toWTFString(toWK(character)) completionBlock:^{
337         if (!m_context)
338             return;
339         m_context->asyncTaskComplete(callbackID);
340     }];
341 }
342
343 void UIScriptController::keyDownUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
344 {
345     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
346
347     // Assumes that the keyboard is already shown.
348     [[HIDEventGenerator sharedHIDEventGenerator] keyDown:toWTFString(toWK(character)) completionBlock:^{
349         if (!m_context)
350             return;
351         m_context->asyncTaskComplete(callbackID);
352     }];
353 }
354
355 void UIScriptController::keyUpUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
356 {
357     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
358
359     // Assumes that the keyboard is already shown.
360     [[HIDEventGenerator sharedHIDEventGenerator] keyUp:toWTFString(toWK(character)) completionBlock:^{
361         if (!m_context)
362             return;
363         m_context->asyncTaskComplete(callbackID);
364     }];
365 }
366
367 void UIScriptController::dismissFormAccessoryView()
368 {
369     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
370     [webView dismissFormAccessoryView];
371 }
372
373 JSRetainPtr<JSStringRef> UIScriptController::selectFormPopoverTitle() const
374 {
375     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
376     return JSStringCreateWithCFString((CFStringRef)webView.selectFormPopoverTitle);
377 }
378
379 JSRetainPtr<JSStringRef> UIScriptController::textContentType() const
380 {
381     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
382     return JSStringCreateWithCFString((CFStringRef)(webView.textContentTypeForTesting ?: @""));
383 }
384
385 JSRetainPtr<JSStringRef> UIScriptController::formInputLabel() const
386 {
387     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
388     return JSStringCreateWithCFString((CFStringRef)webView.formInputLabel);
389 }
390
391 void UIScriptController::selectFormAccessoryPickerRow(long rowIndex)
392 {
393     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
394     [webView selectFormAccessoryPickerRow:rowIndex];
395 }
396
397 void UIScriptController::setTimePickerValue(long hour, long minute)
398 {
399     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
400     [webView setTimePickerValueToHour:hour minute:minute];
401 }
402
403 static CGPoint contentOffsetBoundedInValidRange(UIScrollView *scrollView, CGPoint contentOffset)
404 {
405     UIEdgeInsets contentInsets = scrollView.contentInset;
406     CGSize contentSize = scrollView.contentSize;
407     CGSize scrollViewSize = scrollView.bounds.size;
408
409     CGFloat maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width;
410     contentOffset.x = std::min(maxHorizontalOffset, contentOffset.x);
411     contentOffset.x = std::max(-contentInsets.left, contentOffset.x);
412
413     CGFloat maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height;
414     contentOffset.y = std::min(maxVerticalOffset, contentOffset.y);
415     contentOffset.y = std::max(-contentInsets.top, contentOffset.y);
416     return contentOffset;
417 }
418
419 void UIScriptController::scrollToOffset(long x, long y)
420 {
421     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
422     [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:YES];
423 }
424
425 void UIScriptController::immediateScrollToOffset(long x, long y)
426 {
427     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
428     [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:NO];
429 }
430
431 void UIScriptController::immediateZoomToScale(double scale)
432 {
433     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
434     [webView.scrollView setZoomScale:scale animated:NO];
435 }
436
437 void UIScriptController::keyboardAccessoryBarNext()
438 {
439     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
440     [webView keyboardAccessoryBarNext];
441 }
442
443 void UIScriptController::keyboardAccessoryBarPrevious()
444 {
445     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
446     [webView keyboardAccessoryBarPrevious];
447 }
448
449 bool UIScriptController::isShowingKeyboard() const
450 {
451     return TestController::singleton().mainWebView()->platformView().showingKeyboard;
452 }
453
454 void UIScriptController::applyAutocorrection(JSStringRef newString, JSStringRef oldString, JSValueRef callback)
455 {
456     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
457
458     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
459     [webView applyAutocorrection:toWTFString(toWK(newString)) toString:toWTFString(toWK(oldString)) withCompletionHandler:^ {
460         // applyAutocorrection can call its completion handler synchronously,
461         // which makes UIScriptController unhappy (see bug 172884).
462         dispatch_async(dispatch_get_main_queue(), ^ {
463             if (!m_context)
464                 return;
465             m_context->asyncTaskComplete(callbackID);
466         });
467     }];
468 }
469
470 double UIScriptController::minimumZoomScale() const
471 {
472     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
473     return webView.scrollView.minimumZoomScale;
474 }
475
476 double UIScriptController::maximumZoomScale() const
477 {
478     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
479     return webView.scrollView.maximumZoomScale;
480 }
481
482 std::optional<bool> UIScriptController::stableStateOverride() const
483 {
484     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
485     if (webView._stableStateOverride)
486         return webView._stableStateOverride.boolValue;
487
488     return std::nullopt;
489 }
490
491 void UIScriptController::setStableStateOverride(std::optional<bool> overrideValue)
492 {
493     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
494     if (overrideValue)
495         webView._stableStateOverride = @(overrideValue.value());
496     else
497         webView._stableStateOverride = nil;
498 }
499
500 JSObjectRef UIScriptController::contentVisibleRect() const
501 {
502     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
503
504     CGRect contentVisibleRect = webView._contentVisibleRect;
505     
506     WebCore::FloatRect rect(contentVisibleRect.origin.x, contentVisibleRect.origin.y, contentVisibleRect.size.width, contentVisibleRect.size.height);
507     return m_context->objectFromRect(rect);
508 }
509
510 JSObjectRef UIScriptController::textSelectionRangeRects() const
511 {
512     auto selectionRects = adoptNS([[NSMutableArray alloc] init]);
513     NSArray *rects = TestController::singleton().mainWebView()->platformView()._uiTextSelectionRects;
514     for (NSValue *rect in rects)
515         [selectionRects addObject:toNSDictionary(rect.CGRectValue)];
516
517     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:selectionRects.get() inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
518 }
519
520 JSObjectRef UIScriptController::textSelectionCaretRect() const
521 {
522     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(TestController::singleton().mainWebView()->platformView()._uiTextCaretRect) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
523 }
524
525 JSObjectRef UIScriptController::selectionStartGrabberViewRect() const
526 {
527     WKWebView *webView = TestController::singleton().mainWebView()->platformView();
528     UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
529     UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
530     auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"startGrabber"] frame] toView:contentView];
531     frameInContentCoordinates = CGRectIntersection(contentView.bounds, frameInContentCoordinates);
532     auto jsContext = m_context->jsContext();
533     return JSValueToObject(jsContext, [JSValue valueWithObject:toNSDictionary(frameInContentCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:jsContext]].JSValueRef, nullptr);
534 }
535
536 JSObjectRef UIScriptController::selectionEndGrabberViewRect() const
537 {
538     WKWebView *webView = TestController::singleton().mainWebView()->platformView();
539     UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
540     UIView *selectionRangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
541     auto frameInContentCoordinates = [selectionRangeView convertRect:[[selectionRangeView valueForKeyPath:@"endGrabber"] frame] toView:contentView];
542     frameInContentCoordinates = CGRectIntersection(contentView.bounds, frameInContentCoordinates);
543     auto jsContext = m_context->jsContext();
544     return JSValueToObject(jsContext, [JSValue valueWithObject:toNSDictionary(frameInContentCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:jsContext]].JSValueRef, nullptr);
545 }
546
547 JSObjectRef UIScriptController::selectionCaretViewRect() const
548 {
549     WKWebView *webView = TestController::singleton().mainWebView()->platformView();
550     UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
551     UIView *caretView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.caretView"];
552     auto rectInContentViewCoordinates = CGRectIntersection([caretView convertRect:caretView.bounds toView:contentView], contentView.bounds);
553     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(rectInContentViewCoordinates) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
554 }
555
556 JSObjectRef UIScriptController::selectionRangeViewRects() const
557 {
558     WKWebView *webView = TestController::singleton().mainWebView()->platformView();
559     UIView *contentView = [webView valueForKeyPath:@"_currentContentView"];
560     UIView *rangeView = [contentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView"];
561     auto rectsAsDictionaries = adoptNS([[NSMutableArray alloc] init]);
562     NSArray *textRectInfoArray = [rangeView valueForKeyPath:@"rects"];
563     for (id textRectInfo in textRectInfoArray) {
564         NSValue *rectValue = [textRectInfo valueForKeyPath:@"rect"];
565         auto rangeRectInContentViewCoordinates = [rangeView convertRect:rectValue.CGRectValue toView:contentView];
566         [rectsAsDictionaries addObject:toNSDictionary(CGRectIntersection(rangeRectInContentViewCoordinates, contentView.bounds))];
567     }
568     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:rectsAsDictionaries.get() inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
569 }
570
571 JSObjectRef UIScriptController::inputViewBounds() const
572 {
573     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:toNSDictionary(TestController::singleton().mainWebView()->platformView()._inputViewBounds) inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
574 }
575
576 void UIScriptController::removeAllDynamicDictionaries()
577 {
578     [UIKeyboard removeAllDynamicDictionaries];
579 }
580
581 JSRetainPtr<JSStringRef> UIScriptController::scrollingTreeAsText() const
582 {
583     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
584     return JSStringCreateWithCFString((CFStringRef)[webView _scrollingTreeAsText]);
585 }
586
587 JSObjectRef UIScriptController::propertiesOfLayerWithID(uint64_t layerID) const
588 {
589     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:[TestController::singleton().mainWebView()->platformView() _propertiesOfLayerWithID:layerID] inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
590 }
591
592 static UIDeviceOrientation toUIDeviceOrientation(DeviceOrientation* orientation)
593 {
594     if (!orientation)
595         return UIDeviceOrientationPortrait;
596         
597     switch (*orientation) {
598     case DeviceOrientation::Portrait:
599         return UIDeviceOrientationPortrait;
600     case DeviceOrientation::PortraitUpsideDown:
601         return UIDeviceOrientationPortraitUpsideDown;
602     case DeviceOrientation::LandscapeLeft:
603         return UIDeviceOrientationLandscapeLeft;
604     case DeviceOrientation::LandscapeRight:
605         return UIDeviceOrientationLandscapeRight;
606     }
607     
608     return UIDeviceOrientationPortrait;
609 }
610
611 void UIScriptController::simulateRotation(DeviceOrientation* orientation, JSValueRef callback)
612 {
613     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
614     webView.usesSafariLikeRotation = NO;
615     
616     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
617     
618     webView.rotationDidEndCallback = ^{
619         if (!m_context)
620             return;
621         m_context->asyncTaskComplete(callbackID);
622     };
623     
624     [[UIDevice currentDevice] setOrientation:toUIDeviceOrientation(orientation) animated:YES];
625 }
626
627 void UIScriptController::simulateRotationLikeSafari(DeviceOrientation* orientation, JSValueRef callback)
628 {
629     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
630     webView.usesSafariLikeRotation = YES;
631     
632     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
633     
634     webView.rotationDidEndCallback = ^{
635         if (!m_context)
636             return;
637         m_context->asyncTaskComplete(callbackID);
638     };
639     
640     [[UIDevice currentDevice] setOrientation:toUIDeviceOrientation(orientation) animated:YES];
641 }
642
643 void UIScriptController::platformSetDidStartFormControlInteractionCallback()
644 {
645     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
646     webView.didStartFormControlInteractionCallback = ^{
647         if (!m_context)
648             return;
649         m_context->fireCallback(CallbackTypeDidStartFormControlInteraction);
650     };
651 }
652
653 void UIScriptController::platformSetDidEndFormControlInteractionCallback()
654 {
655     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
656     webView.didEndFormControlInteractionCallback = ^{
657         if (!m_context)
658             return;
659         m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
660     };
661 }
662     
663 void UIScriptController::platformSetDidShowForcePressPreviewCallback()
664 {
665     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
666     webView.didShowForcePressPreviewCallback = ^ {
667         if (!m_context)
668             return;
669         m_context->fireCallback(CallbackTypeDidShowForcePressPreview);
670     };
671 }
672
673 void UIScriptController::platformSetDidDismissForcePressPreviewCallback()
674 {
675     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
676     webView.didDismissForcePressPreviewCallback = ^ {
677         if (!m_context)
678             return;
679         m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
680     };
681 }
682
683 void UIScriptController::platformSetWillBeginZoomingCallback()
684 {
685     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
686     webView.willBeginZoomingCallback = ^{
687         if (!m_context)
688             return;
689         m_context->fireCallback(CallbackTypeWillBeginZooming);
690     };
691 }
692
693 void UIScriptController::platformSetDidEndZoomingCallback()
694 {
695     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
696     webView.didEndZoomingCallback = ^{
697         if (!m_context)
698             return;
699         m_context->fireCallback(CallbackTypeDidEndZooming);
700     };
701 }
702
703 void UIScriptController::platformSetDidShowKeyboardCallback()
704 {
705     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
706     webView.didShowKeyboardCallback = ^{
707         if (!m_context)
708             return;
709         m_context->fireCallback(CallbackTypeDidShowKeyboard);
710     };
711 }
712
713 void UIScriptController::platformSetDidHideKeyboardCallback()
714 {
715     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
716     webView.didHideKeyboardCallback = ^{
717         if (!m_context)
718             return;
719         m_context->fireCallback(CallbackTypeDidHideKeyboard);
720     };
721 }
722
723 void UIScriptController::platformSetDidEndScrollingCallback()
724 {
725     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
726     webView.didEndScrollingCallback = ^{
727         if (!m_context)
728             return;
729         m_context->fireCallback(CallbackTypeDidEndScrolling);
730     };
731 }
732
733 void UIScriptController::platformClearAllCallbacks()
734 {
735     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
736     
737     webView.didStartFormControlInteractionCallback = nil;
738     webView.didEndFormControlInteractionCallback = nil;
739     webView.didShowForcePressPreviewCallback = nil;
740     webView.didDismissForcePressPreviewCallback = nil;
741     webView.didEndZoomingCallback = nil;
742     webView.willBeginZoomingCallback = nil;
743     webView.didHideKeyboardCallback = nil;
744     webView.didShowKeyboardCallback = nil;
745     webView.didEndScrollingCallback = nil;
746     webView.rotationDidEndCallback = nil;
747 }
748
749 void UIScriptController::setSafeAreaInsets(double top, double right, double bottom, double left)
750 {
751     UIEdgeInsets insets = UIEdgeInsetsMake(top, left, bottom, right);
752     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
753     webView.overrideSafeAreaInsets = insets;
754 }
755
756 void UIScriptController::beginBackSwipe(JSValueRef callback)
757 {
758     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
759     [webView _beginBackSwipeForTesting];
760
761     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
762     m_context->asyncTaskComplete(callbackID);
763 }
764
765 void UIScriptController::completeBackSwipe(JSValueRef callback)
766 {
767     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
768     [webView _completeBackSwipeForTesting];
769
770     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
771     m_context->asyncTaskComplete(callbackID);
772 }
773
774 static BOOL forEachViewInHierarchy(UIView *view, void(^mapFunction)(UIView *subview, BOOL *stop))
775 {
776     BOOL stop = NO;
777     mapFunction(view, &stop);
778     if (stop)
779         return YES;
780
781     for (UIView *subview in view.subviews) {
782         stop = forEachViewInHierarchy(subview, mapFunction);
783         if (stop)
784             break;
785     }
786     return stop;
787 }
788
789 bool UIScriptController::isShowingDataListSuggestions() const
790 {
791     Class remoteKeyboardWindowClass = NSClassFromString(@"UIRemoteKeyboardWindow");
792     Class suggestionsPickerViewClass = NSClassFromString(@"WKDataListSuggestionsPickerView");
793     UIWindow *remoteInputHostingWindow = nil;
794     for (UIWindow *window in UIApplication.sharedApplication.windows) {
795         if ([window isKindOfClass:remoteKeyboardWindowClass])
796             remoteInputHostingWindow = window;
797     }
798
799     if (!remoteInputHostingWindow)
800         return false;
801
802     __block bool foundDataListSuggestionsPickerView = false;
803     forEachViewInHierarchy(remoteInputHostingWindow, ^(UIView *subview, BOOL *stop) {
804         if (![subview isKindOfClass:suggestionsPickerViewClass])
805             return;
806
807         foundDataListSuggestionsPickerView = true;
808         *stop = YES;
809     });
810     return foundDataListSuggestionsPickerView;
811 }
812
813 #if HAVE(PENCILKIT)
814 static PKCanvasView *findEditableImageCanvas()
815 {
816     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
817     Class pkCanvasViewClass = NSClassFromString(@"PKCanvasView");
818     __block PKCanvasView *canvasView = nil;
819     forEachViewInHierarchy(webView.window, ^(UIView *subview, BOOL *stop) {
820         if (![subview isKindOfClass:pkCanvasViewClass])
821             return;
822
823         canvasView = (PKCanvasView *)subview;
824         *stop = YES;
825     });
826     return canvasView;
827 }
828 #endif
829
830 void UIScriptController::drawSquareInEditableImage()
831 {
832 #if HAVE(PENCILKIT)
833     Class pkDrawingClass = NSClassFromString(@"PKDrawing");
834     Class pkInkClass = NSClassFromString(@"PKInk");
835     Class pkStrokeClass = NSClassFromString(@"PKStroke");
836
837     PKCanvasView *canvasView = findEditableImageCanvas();
838     RetainPtr<PKDrawing> drawing = adoptNS([[pkDrawingClass alloc] init]);
839     RetainPtr<CGPathRef> path = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, 50, 50), NULL));
840     RetainPtr<PKInk> ink = [pkInkClass inkWithType:0 color:UIColor.greenColor weight:100.0];
841     RetainPtr<PKStroke> stroke = adoptNS([[pkStrokeClass alloc] _initWithPath:path.get() ink:ink.get() inputScale:1]);
842     [drawing _addStroke:stroke.get()];
843
844     [canvasView setDrawing:drawing.get()];
845 #endif
846 }
847
848 long UIScriptController::numberOfStrokesInEditableImage()
849 {
850 #if HAVE(PENCILKIT)
851     PKCanvasView *canvasView = findEditableImageCanvas();
852     return canvasView.drawing._allStrokes.count;
853 #else
854     return 0;
855 #endif
856 }
857
858 }
859
860 #endif // PLATFORM(IOS_FAMILY)