2 * Copyright (C) 2015 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "UIScriptController.h"
31 #import "HIDEventGenerator.h"
32 #import "PlatformWebView.h"
33 #import "StringFunctions.h"
34 #import "TestController.h"
35 #import "TestRunnerWKWebView.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>
47 void UIScriptController::doAsyncTask(JSValueRef callback)
49 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
51 dispatch_async(dispatch_get_main_queue(), ^{
54 m_context->asyncTaskComplete(callbackID);
58 void UIScriptController::doAfterPresentationUpdate(JSValueRef callback)
60 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
62 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
63 [webView _doAfterNextPresentationUpdate:^{
66 m_context->asyncTaskComplete(callbackID);
70 void UIScriptController::zoomToScale(double scale, JSValueRef callback)
72 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
74 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
76 [webView zoomToScale:scale animated:YES completionHandler:^{
79 m_context->asyncTaskComplete(callbackID);
83 void UIScriptController::simulateAccessibilitySettingsChangeNotification(JSValueRef callback)
85 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
87 auto* webView = TestController::singleton().mainWebView()->platformView();
88 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
89 [center postNotificationName:UIAccessibilityInvertColorsStatusDidChangeNotification object:webView];
91 [webView _doAfterNextPresentationUpdate: ^{
94 m_context->asyncTaskComplete(callbackID);
98 double UIScriptController::zoomScale() const
100 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
101 return webView.scrollView.zoomScale;
104 static CGPoint globalToContentCoordinates(TestRunnerWKWebView *webView, long x, long y)
106 CGPoint point = CGPointMake(x, y);
107 point = [webView _convertPointFromContentsToView:point];
108 point = [webView convertPoint:point toView:nil];
109 point = [webView.window convertPoint:point toWindow:nil];
113 void UIScriptController::touchDownAtPoint(long x, long y, long touchCount, JSValueRef callback)
115 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
117 auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
118 [[HIDEventGenerator sharedHIDEventGenerator] touchDown:location touchCount:touchCount completionBlock:^{
121 m_context->asyncTaskComplete(callbackID);
125 void UIScriptController::liftUpAtPoint(long x, long y, long touchCount, JSValueRef callback)
127 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
129 auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
130 [[HIDEventGenerator sharedHIDEventGenerator] liftUp:location touchCount:touchCount completionBlock:^{
133 m_context->asyncTaskComplete(callbackID);
137 void UIScriptController::singleTapAtPoint(long x, long y, JSValueRef callback)
139 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
141 [[HIDEventGenerator sharedHIDEventGenerator] tap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
144 m_context->asyncTaskComplete(callbackID);
148 void UIScriptController::doubleTapAtPoint(long x, long y, JSValueRef callback)
150 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
152 [[HIDEventGenerator sharedHIDEventGenerator] doubleTap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
155 m_context->asyncTaskComplete(callbackID);
159 void UIScriptController::stylusDownAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
161 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
163 auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
164 [[HIDEventGenerator sharedHIDEventGenerator] stylusDownAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
167 m_context->asyncTaskComplete(callbackID);
171 void UIScriptController::stylusMoveToPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
173 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
175 auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
176 [[HIDEventGenerator sharedHIDEventGenerator] stylusMoveToPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
179 m_context->asyncTaskComplete(callbackID);
183 void UIScriptController::stylusUpAtPoint(long x, long y, JSValueRef callback)
185 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
187 auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
188 [[HIDEventGenerator sharedHIDEventGenerator] stylusUpAtPoint:location completionBlock:^{
191 m_context->asyncTaskComplete(callbackID);
195 void UIScriptController::stylusTapAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
197 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
199 auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
200 [[HIDEventGenerator sharedHIDEventGenerator] stylusTapAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
203 m_context->asyncTaskComplete(callbackID);
207 void UIScriptController::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
209 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
211 String jsonString = eventsJSON->string();
212 auto eventInfo = dynamic_objc_cast<NSDictionary>([NSJSONSerialization JSONObjectWithData:[(NSString *)jsonString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]);
213 if (!eventInfo || ![eventInfo isKindOfClass:[NSDictionary class]]) {
214 WTFLogAlways("JSON is not convertible to a dictionary");
218 [[HIDEventGenerator sharedHIDEventGenerator] sendEventStream:eventInfo completionBlock:^{
221 m_context->asyncTaskComplete(callbackID);
225 void UIScriptController::dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, JSValueRef callback)
227 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
229 CGPoint startPoint = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), startX, startY);
230 CGPoint endPoint = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), endX, endY);
232 [[HIDEventGenerator sharedHIDEventGenerator] dragWithStartPoint:startPoint endPoint:endPoint duration:durationSeconds completionBlock:^{
235 m_context->asyncTaskComplete(callbackID);
239 void UIScriptController::longPressAtPoint(long x, long y, JSValueRef callback)
241 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
243 [[HIDEventGenerator sharedHIDEventGenerator] longPress:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
246 m_context->asyncTaskComplete(callbackID);
250 void UIScriptController::typeCharacterUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
252 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
254 // Assumes that the keyboard is already shown.
255 [[HIDEventGenerator sharedHIDEventGenerator] keyPress:toWTFString(toWK(character)) completionBlock:^{
258 m_context->asyncTaskComplete(callbackID);
262 void UIScriptController::keyDownUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
264 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
266 // Assumes that the keyboard is already shown.
267 [[HIDEventGenerator sharedHIDEventGenerator] keyDown:toWTFString(toWK(character)) completionBlock:^{
270 m_context->asyncTaskComplete(callbackID);
274 void UIScriptController::keyUpUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
276 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
278 // Assumes that the keyboard is already shown.
279 [[HIDEventGenerator sharedHIDEventGenerator] keyUp:toWTFString(toWK(character)) completionBlock:^{
282 m_context->asyncTaskComplete(callbackID);
286 void UIScriptController::selectTextCandidateAtIndex(long index, JSValueRef callback)
288 #if USE(APPLE_INTERNAL_SDK)
289 static const float textPredictionsPollingInterval = 0.1;
290 unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
291 waitForTextPredictionsViewAndSelectCandidateAtIndex(index, callbackID, textPredictionsPollingInterval);
293 // FIXME: This is a no-op on non-internal builds due to UIKeyboardPredictionView being unavailable. Ideally, there should be a better way to
294 // retrieve information and interact with the predictive text view that will be compatible with OpenSource.
296 UNUSED_PARAM(callback);
300 void UIScriptController::waitForTextPredictionsViewAndSelectCandidateAtIndex(long index, unsigned callbackID, float interval)
302 id UIKeyboardPredictionViewClass = NSClassFromString(@"UIKeyboardPredictionView");
303 if (!UIKeyboardPredictionViewClass)
306 #if USE(APPLE_INTERNAL_SDK)
307 UIKeyboardPredictionView *predictionView = (UIKeyboardPredictionView *)[UIKeyboardPredictionViewClass activeInstance];
308 if (![predictionView hasPredictions]) {
309 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^() {
310 waitForTextPredictionsViewAndSelectCandidateAtIndex(index, callbackID, interval);
315 PlatformWKView webView = TestController::singleton().mainWebView()->platformView();
316 CGRect predictionViewFrame = [predictionView frame];
317 // This assumes there are 3 predicted text cells of equal width, which is the case on iOS.
318 float offsetX = (index * 2 + 1) * CGRectGetWidth(predictionViewFrame) / 6;
319 float offsetY = CGRectGetHeight(webView.window.frame) - CGRectGetHeight([[predictionView superview] frame]) + CGRectGetHeight(predictionViewFrame) / 2;
320 [[HIDEventGenerator sharedHIDEventGenerator] tap:CGPointMake(offsetX, offsetY) completionBlock:^{
322 m_context->asyncTaskComplete(callbackID);
326 UNUSED_PARAM(callbackID);
327 UNUSED_PARAM(interval);
331 void UIScriptController::dismissFormAccessoryView()
333 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
334 [webView dismissFormAccessoryView];
337 void UIScriptController::selectFormAccessoryPickerRow(long rowIndex)
339 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
340 [webView selectFormAccessoryPickerRow:rowIndex];
343 JSObjectRef UIScriptController::contentsOfUserInterfaceItem(JSStringRef interfaceItem) const
345 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
346 NSDictionary *contentDictionary = [webView _contentsOfUserInterfaceItem:toWTFString(toWK(interfaceItem))];
347 return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:contentDictionary inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
350 static CGPoint contentOffsetBoundedInValidRange(UIScrollView *scrollView, CGPoint contentOffset)
352 UIEdgeInsets contentInsets = scrollView.contentInset;
353 CGSize contentSize = scrollView.contentSize;
354 CGSize scrollViewSize = scrollView.bounds.size;
356 CGFloat maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width;
357 contentOffset.x = std::min(maxHorizontalOffset, contentOffset.x);
358 contentOffset.x = std::max(-contentInsets.left, contentOffset.x);
360 CGFloat maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height;
361 contentOffset.y = std::min(maxVerticalOffset, contentOffset.y);
362 contentOffset.y = std::max(-contentInsets.top, contentOffset.y);
363 return contentOffset;
366 void UIScriptController::scrollToOffset(long x, long y)
368 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
369 [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:YES];
372 void UIScriptController::immediateScrollToOffset(long x, long y)
374 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
375 [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:NO];
378 void UIScriptController::immediateZoomToScale(double scale)
380 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
381 [webView.scrollView setZoomScale:scale animated:NO];
384 void UIScriptController::keyboardAccessoryBarNext()
386 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
387 [webView keyboardAccessoryBarNext];
390 void UIScriptController::keyboardAccessoryBarPrevious()
392 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
393 [webView keyboardAccessoryBarPrevious];
396 double UIScriptController::minimumZoomScale() const
398 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
399 return webView.scrollView.minimumZoomScale;
402 double UIScriptController::maximumZoomScale() const
404 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
405 return webView.scrollView.maximumZoomScale;
408 Optional<bool> UIScriptController::stableStateOverride() const
410 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
411 if (webView._stableStateOverride)
412 return webView._stableStateOverride.boolValue;
417 void UIScriptController::setStableStateOverride(Optional<bool> overrideValue)
419 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
421 webView._stableStateOverride = @(overrideValue.value());
423 webView._stableStateOverride = nil;
426 JSObjectRef UIScriptController::contentVisibleRect() const
428 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
430 CGRect contentVisibleRect = webView._contentVisibleRect;
432 WebCore::FloatRect rect(contentVisibleRect.origin.x, contentVisibleRect.origin.y, contentVisibleRect.size.width, contentVisibleRect.size.height);
433 return m_context->objectFromRect(rect);
436 JSObjectRef UIScriptController::selectionRangeViewRects() const
438 NSMutableArray *selectionRects = [[NSMutableArray alloc] init];
439 for (UIView *rectView in TestController::singleton().mainWebView()->platformView()._uiTextSelectionRectViews) {
443 CGRect frame = rectView.frame;
444 [selectionRects addObject:@{
445 @"left": @(frame.origin.x),
446 @"top": @(frame.origin.y),
447 @"width": @(frame.size.width),
448 @"height": @(frame.size.height),
451 return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:selectionRects inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
454 void UIScriptController::removeAllDynamicDictionaries()
456 [UIKeyboard removeAllDynamicDictionaries];
459 JSRetainPtr<JSStringRef> UIScriptController::scrollingTreeAsText() const
461 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
462 return JSStringCreateWithCFString((CFStringRef)[webView _scrollingTreeAsText]);
465 void UIScriptController::platformSetDidStartFormControlInteractionCallback()
467 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
468 webView.didStartFormControlInteractionCallback = ^{
471 m_context->fireCallback(CallbackTypeDidStartFormControlInteraction);
475 void UIScriptController::platformSetDidEndFormControlInteractionCallback()
477 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
478 webView.didEndFormControlInteractionCallback = ^{
481 m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
485 void UIScriptController::platformSetDidShowForcePressPreviewCallback()
487 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
488 webView.didShowForcePressPreviewCallback = ^ {
491 m_context->fireCallback(CallbackTypeDidShowForcePressPreview);
495 void UIScriptController::platformSetDidDismissForcePressPreviewCallback()
497 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
498 webView.didDismissForcePressPreviewCallback = ^ {
501 m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
505 void UIScriptController::platformSetWillBeginZoomingCallback()
507 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
508 webView.willBeginZoomingCallback = ^{
511 m_context->fireCallback(CallbackTypeWillBeginZooming);
515 void UIScriptController::platformSetDidEndZoomingCallback()
517 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
518 webView.didEndZoomingCallback = ^{
521 m_context->fireCallback(CallbackTypeDidEndZooming);
525 void UIScriptController::platformSetDidShowKeyboardCallback()
527 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
528 webView.didShowKeyboardCallback = ^{
531 m_context->fireCallback(CallbackTypeDidShowKeyboard);
535 void UIScriptController::platformSetDidHideKeyboardCallback()
537 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
538 webView.didHideKeyboardCallback = ^{
541 m_context->fireCallback(CallbackTypeDidHideKeyboard);
545 void UIScriptController::platformSetDidEndScrollingCallback()
547 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
548 webView.didEndScrollingCallback = ^{
551 m_context->fireCallback(CallbackTypeDidEndScrolling);
555 void UIScriptController::platformClearAllCallbacks()
557 TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
559 webView.didStartFormControlInteractionCallback = nil;
560 webView.didEndFormControlInteractionCallback = nil;
561 webView.didShowForcePressPreviewCallback = nil;
562 webView.didDismissForcePressPreviewCallback = nil;
563 webView.didEndZoomingCallback = nil;
564 webView.willBeginZoomingCallback = nil;
565 webView.didHideKeyboardCallback = nil;
566 webView.didShowKeyboardCallback = nil;
567 webView.didEndScrollingCallback = nil;
572 #endif // PLATFORM(IOS)