[WK2] autocorrect and autocapitalize attributes do not work in contenteditable elements
[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 void UIScriptController::doAsyncTask(JSValueRef callback)
48 {
49     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
50
51     dispatch_async(dispatch_get_main_queue(), ^{
52         if (!m_context)
53             return;
54         m_context->asyncTaskComplete(callbackID);
55     });
56 }
57
58 void UIScriptController::zoomToScale(double scale, JSValueRef callback)
59 {
60     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
61
62     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
63
64     [webView zoomToScale:scale animated:YES completionHandler:^{
65         if (!m_context)
66             return;
67         m_context->asyncTaskComplete(callbackID);
68     }];
69 }
70
71 double UIScriptController::zoomScale() const
72 {
73     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
74     return webView.scrollView.zoomScale;
75 }
76
77 static CGPoint globalToContentCoordinates(TestRunnerWKWebView *webView, long x, long y)
78 {
79     CGPoint point = CGPointMake(x, y);
80     point = [webView _convertPointFromContentsToView:point];
81     point = [webView convertPoint:point toView:nil];
82     point = [webView.window convertPoint:point toWindow:nil];
83     return point;
84 }
85
86 void UIScriptController::touchDownAtPoint(long x, long y, long touchCount, JSValueRef callback)
87 {
88     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
89
90     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
91     [[HIDEventGenerator sharedHIDEventGenerator] touchDown:location touchCount:touchCount completionBlock:^{
92         if (!m_context)
93             return;
94         m_context->asyncTaskComplete(callbackID);
95     }];
96 }
97
98 void UIScriptController::liftUpAtPoint(long x, long y, long touchCount, JSValueRef callback)
99 {
100     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
101     
102     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
103     [[HIDEventGenerator sharedHIDEventGenerator] liftUp:location touchCount:touchCount completionBlock:^{
104         if (!m_context)
105             return;
106         m_context->asyncTaskComplete(callbackID);
107     }];
108 }
109
110 void UIScriptController::singleTapAtPoint(long x, long y, JSValueRef callback)
111 {
112     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
113
114     [[HIDEventGenerator sharedHIDEventGenerator] tap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
115         if (!m_context)
116             return;
117         m_context->asyncTaskComplete(callbackID);
118     }];
119 }
120
121 void UIScriptController::doubleTapAtPoint(long x, long y, JSValueRef callback)
122 {
123     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
124
125     [[HIDEventGenerator sharedHIDEventGenerator] doubleTap:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
126         if (!m_context)
127             return;
128         m_context->asyncTaskComplete(callbackID);
129     }];
130 }
131
132 void UIScriptController::stylusDownAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
133 {
134     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
135
136     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
137     [[HIDEventGenerator sharedHIDEventGenerator] stylusDownAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
138         if (!m_context)
139             return;
140         m_context->asyncTaskComplete(callbackID);
141     }];
142 }
143
144 void UIScriptController::stylusMoveToPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
145 {
146     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
147
148     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
149     [[HIDEventGenerator sharedHIDEventGenerator] stylusMoveToPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
150         if (!m_context)
151             return;
152         m_context->asyncTaskComplete(callbackID);
153     }];
154 }
155
156 void UIScriptController::stylusUpAtPoint(long x, long y, JSValueRef callback)
157 {
158     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
159
160     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
161     [[HIDEventGenerator sharedHIDEventGenerator] stylusUpAtPoint:location completionBlock:^{
162         if (!m_context)
163             return;
164         m_context->asyncTaskComplete(callbackID);
165     }];
166 }
167
168 void UIScriptController::stylusTapAtPoint(long x, long y, float azimuthAngle, float altitudeAngle, float pressure, JSValueRef callback)
169 {
170     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
171
172     auto location = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y);
173     [[HIDEventGenerator sharedHIDEventGenerator] stylusTapAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure completionBlock:^{
174         if (!m_context)
175             return;
176         m_context->asyncTaskComplete(callbackID);
177     }];
178 }
179
180 void UIScriptController::sendEventStream(JSStringRef eventsJSON, JSValueRef callback)
181 {
182     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
183
184     String jsonString = eventsJSON->string();
185     auto eventInfo = dynamic_objc_cast<NSDictionary>([NSJSONSerialization JSONObjectWithData:[(NSString *)jsonString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]);
186     if (!eventInfo || ![eventInfo isKindOfClass:[NSDictionary class]]) {
187         WTFLogAlways("JSON is not convertible to a dictionary");
188         return;
189     }
190     
191     [[HIDEventGenerator sharedHIDEventGenerator] sendEventStream:eventInfo completionBlock:^{
192         if (!m_context)
193             return;
194         m_context->asyncTaskComplete(callbackID);
195     }];
196 }
197
198 void UIScriptController::dragFromPointToPoint(long startX, long startY, long endX, long endY, double durationSeconds, JSValueRef callback)
199 {
200     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
201
202     CGPoint startPoint = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), startX, startY);
203     CGPoint endPoint = globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), endX, endY);
204     
205     [[HIDEventGenerator sharedHIDEventGenerator] dragWithStartPoint:startPoint endPoint:endPoint duration:durationSeconds completionBlock:^{
206         if (!m_context)
207             return;
208         m_context->asyncTaskComplete(callbackID);
209     }];
210 }
211     
212 void UIScriptController::longPressAtPoint(long x, long y, JSValueRef callback)
213 {
214     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
215     
216     [[HIDEventGenerator sharedHIDEventGenerator] longPress:globalToContentCoordinates(TestController::singleton().mainWebView()->platformView(), x, y) completionBlock:^{
217         if (!m_context)
218             return;
219         m_context->asyncTaskComplete(callbackID);
220     }];
221 }
222
223 void UIScriptController::typeCharacterUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
224 {
225     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
226
227     // Assumes that the keyboard is already shown.
228     [[HIDEventGenerator sharedHIDEventGenerator] keyPress:toWTFString(toWK(character)) completionBlock:^{
229         if (!m_context)
230             return;
231         m_context->asyncTaskComplete(callbackID);
232     }];
233 }
234
235 void UIScriptController::keyDownUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
236 {
237     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
238
239     // Assumes that the keyboard is already shown.
240     [[HIDEventGenerator sharedHIDEventGenerator] keyDown:toWTFString(toWK(character)) completionBlock:^{
241         if (!m_context)
242             return;
243         m_context->asyncTaskComplete(callbackID);
244     }];
245 }
246
247 void UIScriptController::keyUpUsingHardwareKeyboard(JSStringRef character, JSValueRef callback)
248 {
249     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
250
251     // Assumes that the keyboard is already shown.
252     [[HIDEventGenerator sharedHIDEventGenerator] keyUp:toWTFString(toWK(character)) completionBlock:^{
253         if (!m_context)
254             return;
255         m_context->asyncTaskComplete(callbackID);
256     }];
257 }
258
259 void UIScriptController::selectTextCandidateAtIndex(long index, JSValueRef callback)
260 {
261 #if USE(APPLE_INTERNAL_SDK)
262     static const float textPredictionsPollingInterval = 0.1;
263     unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
264     waitForTextPredictionsViewAndSelectCandidateAtIndex(index, callbackID, textPredictionsPollingInterval);
265 #else
266     // FIXME: This is a no-op on non-internal builds due to UIKeyboardPredictionView being unavailable. Ideally, there should be a better way to
267     // retrieve information and interact with the predictive text view that will be compatible with OpenSource.
268     UNUSED_PARAM(index);
269     UNUSED_PARAM(callback);
270 #endif
271 }
272
273 void UIScriptController::waitForTextPredictionsViewAndSelectCandidateAtIndex(long index, unsigned callbackID, float interval)
274 {
275     id UIKeyboardPredictionViewClass = NSClassFromString(@"UIKeyboardPredictionView");
276     if (!UIKeyboardPredictionViewClass)
277         return;
278
279 #if USE(APPLE_INTERNAL_SDK)
280     UIKeyboardPredictionView *predictionView = (UIKeyboardPredictionView *)[UIKeyboardPredictionViewClass activeInstance];
281     if (![predictionView hasPredictions]) {
282         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^() {
283             waitForTextPredictionsViewAndSelectCandidateAtIndex(index, callbackID, interval);
284         });
285         return;
286     }
287
288     PlatformWKView webView = TestController::singleton().mainWebView()->platformView();
289     CGRect predictionViewFrame = [predictionView frame];
290     // This assumes there are 3 predicted text cells of equal width, which is the case on iOS.
291     float offsetX = (index * 2 + 1) * CGRectGetWidth(predictionViewFrame) / 6;
292     float offsetY = CGRectGetHeight(webView.window.frame) - CGRectGetHeight([[predictionView superview] frame]) + CGRectGetHeight(predictionViewFrame) / 2;
293     [[HIDEventGenerator sharedHIDEventGenerator] tap:CGPointMake(offsetX, offsetY) completionBlock:^{
294         if (m_context)
295             m_context->asyncTaskComplete(callbackID);
296     }];
297 #else
298     UNUSED_PARAM(index);
299     UNUSED_PARAM(callbackID);
300     UNUSED_PARAM(interval);
301 #endif
302 }
303
304 void UIScriptController::dismissFormAccessoryView()
305 {
306     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
307     [webView dismissFormAccessoryView];
308 }
309
310 void UIScriptController::selectFormAccessoryPickerRow(long rowIndex)
311 {
312     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
313     [webView selectFormAccessoryPickerRow:rowIndex];
314 }
315     
316 JSObjectRef UIScriptController::contentsOfUserInterfaceItem(JSStringRef interfaceItem) const
317 {
318     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
319     NSDictionary *contentDictionary = [webView _contentsOfUserInterfaceItem:toWTFString(toWK(interfaceItem))];
320     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:contentDictionary inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
321 }
322
323 static CGPoint contentOffsetBoundedInValidRange(UIScrollView *scrollView, CGPoint contentOffset)
324 {
325     UIEdgeInsets contentInsets = scrollView.contentInset;
326     CGSize contentSize = scrollView.contentSize;
327     CGSize scrollViewSize = scrollView.bounds.size;
328
329     CGFloat maxHorizontalOffset = contentSize.width + contentInsets.right - scrollViewSize.width;
330     contentOffset.x = std::min(maxHorizontalOffset, contentOffset.x);
331     contentOffset.x = std::max(-contentInsets.left, contentOffset.x);
332
333     CGFloat maxVerticalOffset = contentSize.height + contentInsets.bottom - scrollViewSize.height;
334     contentOffset.y = std::min(maxVerticalOffset, contentOffset.y);
335     contentOffset.y = std::max(-contentInsets.top, contentOffset.y);
336     return contentOffset;
337 }
338
339 void UIScriptController::scrollToOffset(long x, long y)
340 {
341     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
342     [webView.scrollView setContentOffset:contentOffsetBoundedInValidRange(webView.scrollView, CGPointMake(x, y)) animated:YES];
343 }
344
345 void UIScriptController::keyboardAccessoryBarNext()
346 {
347     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
348     [webView keyboardAccessoryBarNext];
349 }
350
351 void UIScriptController::keyboardAccessoryBarPrevious()
352 {
353     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
354     [webView keyboardAccessoryBarPrevious];
355 }
356
357 double UIScriptController::minimumZoomScale() const
358 {
359     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
360     return webView.scrollView.minimumZoomScale;
361 }
362
363 double UIScriptController::maximumZoomScale() const
364 {
365     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
366     return webView.scrollView.maximumZoomScale;
367 }
368
369 JSObjectRef UIScriptController::contentVisibleRect() const
370 {
371     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
372
373     CGRect contentVisibleRect = webView._contentVisibleRect;
374     
375     WebCore::FloatRect rect(contentVisibleRect.origin.x, contentVisibleRect.origin.y, contentVisibleRect.size.width, contentVisibleRect.size.height);
376     return m_context->objectFromRect(rect);
377 }
378
379 JSObjectRef UIScriptController::selectionRangeViewRects() const
380 {
381     NSMutableArray *selectionRects = [[NSMutableArray alloc] init];
382     for (UIView *rectView in TestController::singleton().mainWebView()->platformView()._uiTextSelectionRectViews) {
383         if (rectView.hidden)
384             continue;
385
386         CGRect frame = rectView.frame;
387         [selectionRects addObject:@{
388             @"left": @(frame.origin.x),
389             @"top": @(frame.origin.y),
390             @"width": @(frame.size.width),
391             @"height": @(frame.size.height),
392         }];
393     }
394     return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:selectionRects inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr);
395 }
396
397 void UIScriptController::removeAllDynamicDictionaries()
398 {
399     [UIKeyboard removeAllDynamicDictionaries];
400 }
401
402 void UIScriptController::platformSetDidStartFormControlInteractionCallback()
403 {
404     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
405     webView.didStartFormControlInteractionCallback = ^{
406         if (!m_context)
407             return;
408         m_context->fireCallback(CallbackTypeDidStartFormControlInteraction);
409     };
410 }
411
412 void UIScriptController::platformSetDidEndFormControlInteractionCallback()
413 {
414     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
415     webView.didEndFormControlInteractionCallback = ^{
416         if (!m_context)
417             return;
418         m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
419     };
420 }
421     
422 void UIScriptController::platformSetDidShowForcePressPreviewCallback()
423 {
424     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
425     webView.didShowForcePressPreviewCallback = ^ {
426         if (!m_context)
427             return;
428         m_context->fireCallback(CallbackTypeDidShowForcePressPreview);
429     };
430 }
431
432 void UIScriptController::platformSetDidDismissForcePressPreviewCallback()
433 {
434     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
435     webView.didDismissForcePressPreviewCallback = ^ {
436         if (!m_context)
437             return;
438         m_context->fireCallback(CallbackTypeDidEndFormControlInteraction);
439     };
440 }
441
442 void UIScriptController::platformSetWillBeginZoomingCallback()
443 {
444     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
445     webView.willBeginZoomingCallback = ^{
446         if (!m_context)
447             return;
448         m_context->fireCallback(CallbackTypeWillBeginZooming);
449     };
450 }
451
452 void UIScriptController::platformSetDidEndZoomingCallback()
453 {
454     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
455     webView.didEndZoomingCallback = ^{
456         if (!m_context)
457             return;
458         m_context->fireCallback(CallbackTypeDidEndZooming);
459     };
460 }
461
462 void UIScriptController::platformSetDidShowKeyboardCallback()
463 {
464     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
465     webView.didShowKeyboardCallback = ^{
466         if (!m_context)
467             return;
468         m_context->fireCallback(CallbackTypeDidShowKeyboard);
469     };
470 }
471
472 void UIScriptController::platformSetDidHideKeyboardCallback()
473 {
474     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
475     webView.didHideKeyboardCallback = ^{
476         if (!m_context)
477             return;
478         m_context->fireCallback(CallbackTypeDidHideKeyboard);
479     };
480 }
481
482 void UIScriptController::platformSetDidEndScrollingCallback()
483 {
484     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
485     webView.didEndScrollingCallback = ^{
486         if (!m_context)
487             return;
488         m_context->fireCallback(CallbackTypeDidEndScrolling);
489     };
490 }
491
492 void UIScriptController::platformClearAllCallbacks()
493 {
494     TestRunnerWKWebView *webView = TestController::singleton().mainWebView()->platformView();
495     webView.didEndZoomingCallback = nil;
496     webView.willBeginZoomingCallback = nil;
497     webView.didHideKeyboardCallback = nil;
498     webView.didShowKeyboardCallback = nil;
499     webView.didEndScrollingCallback = nil;
500 }
501
502 }
503
504 #endif // PLATFORM(IOS)