Allow pages to trigger programmatic paste from script on iOS
[WebKit-https.git] / Tools / WebKitTestRunner / ios / TestControllerIOS.mm
1 /*
2  * Copyright (C) 2014 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 "TestController.h"
28
29 #import "GeneratedTouchesDebugWindow.h"
30 #import "HIDEventGenerator.h"
31 #import "IOSLayoutTestCommunication.h"
32 #import "PlatformWebView.h"
33 #import "TestInvocation.h"
34 #import "TestRunnerWKWebView.h"
35 #import "UIKitSPI.h"
36 #import <Foundation/Foundation.h>
37 #import <UIKit/UIKit.h>
38 #import <WebKit/WKPreferencesPrivate.h>
39 #import <WebKit/WKPreferencesRefPrivate.h>
40 #import <WebKit/WKProcessPoolPrivate.h>
41 #import <WebKit/WKStringCF.h>
42 #import <WebKit/WKUserContentControllerPrivate.h>
43 #import <WebKit/WKWebViewConfigurationPrivate.h>
44 #import <WebKit/WKWebViewPrivate.h>
45 #import <objc/runtime.h>
46 #import <wtf/MainThread.h>
47
48 static BOOL overrideIsInHardwareKeyboardMode()
49 {
50     return NO;
51 }
52
53 namespace WTR {
54
55 static bool isDoneWaitingForKeyboardToDismiss = true;
56 static bool isDoneWaitingForMenuToDismiss = true;
57
58 static void handleKeyboardWillHideNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
59 {
60     isDoneWaitingForKeyboardToDismiss = false;
61 }
62
63 static void handleKeyboardDidHideNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
64 {
65     isDoneWaitingForKeyboardToDismiss = true;
66 }
67
68 static void handleMenuWillHideNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
69 {
70     isDoneWaitingForMenuToDismiss = false;
71 }
72
73 static void handleMenuDidHideNotification(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
74 {
75     isDoneWaitingForMenuToDismiss = true;
76 }
77
78 void TestController::notifyDone()
79 {
80 }
81
82 void TestController::platformInitialize()
83 {
84     setUpIOSLayoutTestCommunication();
85     cocoaPlatformInitialize();
86
87     [UIApplication sharedApplication].idleTimerDisabled = YES;
88     [[UIScreen mainScreen] _setScale:2.0];
89
90     auto center = CFNotificationCenterGetLocalCenter();
91     CFNotificationCenterAddObserver(center, this, handleKeyboardWillHideNotification, (CFStringRef)UIKeyboardWillHideNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
92     CFNotificationCenterAddObserver(center, this, handleKeyboardDidHideNotification, (CFStringRef)UIKeyboardDidHideNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
93     CFNotificationCenterAddObserver(center, this, handleMenuWillHideNotification, (CFStringRef)UIMenuControllerWillHideMenuNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
94     CFNotificationCenterAddObserver(center, this, handleMenuDidHideNotification, (CFStringRef)UIMenuControllerDidHideMenuNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately);
95
96     // Override the implementation of +[UIKeyboard isInHardwareKeyboardMode] to ensure that test runs are deterministic
97     // regardless of whether a hardware keyboard is attached. We intentionally never restore the original implementation.
98     method_setImplementation(class_getClassMethod([UIKeyboard class], @selector(isInHardwareKeyboardMode)), reinterpret_cast<IMP>(overrideIsInHardwareKeyboardMode));
99 }
100
101 void TestController::platformDestroy()
102 {
103     tearDownIOSLayoutTestCommunication();
104
105     auto center = CFNotificationCenterGetLocalCenter();
106     CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIKeyboardWillHideNotification, nullptr);
107     CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIKeyboardDidHideNotification, nullptr);
108     CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIMenuControllerWillHideMenuNotification, nullptr);
109     CFNotificationCenterRemoveObserver(center, this, (CFStringRef)UIMenuControllerDidHideMenuNotification, nullptr);
110 }
111
112 void TestController::initializeInjectedBundlePath()
113 {
114     NSString *nsBundlePath = [[NSBundle mainBundle].builtInPlugInsPath stringByAppendingPathComponent:@"WebKitTestRunnerInjectedBundle.bundle"];
115     m_injectedBundlePath.adopt(WKStringCreateWithCFString((CFStringRef)nsBundlePath));
116 }
117
118 void TestController::initializeTestPluginDirectory()
119 {
120     m_testPluginDirectory.adopt(WKStringCreateWithCFString((CFStringRef)[[NSBundle mainBundle] bundlePath]));
121 }
122
123 void TestController::configureContentExtensionForTest(const TestInvocation&)
124 {
125 }
126
127 void TestController::platformResetPreferencesToConsistentValues()
128 {
129     WKPreferencesRef preferences = platformPreferences();
130     WKPreferencesSetTextAutosizingEnabled(preferences, false);
131 }
132
133 void TestController::platformResetStateToConsistentValues(const TestOptions& options)
134 {
135     cocoaResetStateToConsistentValues(options);
136
137     UIMenuController.sharedMenuController.menuVisible = NO;
138     [[UIApplication sharedApplication] _cancelAllTouches];
139     [[UIDevice currentDevice] setOrientation:UIDeviceOrientationPortrait animated:NO];
140
141     m_inputModeSwizzlers.clear();
142     m_overriddenKeyboardInputMode = nil;
143     
144     BOOL shouldRestoreFirstResponder = NO;
145     if (PlatformWebView* platformWebView = mainWebView()) {
146         TestRunnerWKWebView *webView = platformWebView->platformView();
147         webView._stableStateOverride = nil;
148         webView.usesSafariLikeRotation = NO;
149         webView.overrideSafeAreaInsets = UIEdgeInsetsZero;
150         [webView _clearOverrideLayoutParameters];
151         [webView _clearInterfaceOrientationOverride];
152
153         UIScrollView *scrollView = webView.scrollView;
154         [scrollView _removeAllAnimations:YES];
155         [scrollView setZoomScale:1 animated:NO];
156         scrollView.contentInset = UIEdgeInsetsMake(options.contentInsetTop, 0, 0, 0);
157         scrollView.contentOffset = CGPointMake(0, -options.contentInsetTop);
158
159         if (webView.interactingWithFormControl)
160             shouldRestoreFirstResponder = [webView resignFirstResponder];
161     }
162
163     runUntil(isDoneWaitingForKeyboardToDismiss, m_currentInvocation->shortTimeout());
164     runUntil(isDoneWaitingForMenuToDismiss, m_currentInvocation->shortTimeout());
165
166     if (shouldRestoreFirstResponder)
167         [mainWebView()->platformView() becomeFirstResponder];
168 }
169
170 void TestController::platformConfigureViewForTest(const TestInvocation& test)
171 {
172     if (!test.options().useFlexibleViewport)
173         return;
174         
175     TestRunnerWKWebView *webView = mainWebView()->platformView();
176
177     if (test.options().shouldIgnoreMetaViewport)
178         webView.configuration.preferences._shouldIgnoreMetaViewport = YES;
179
180     CGRect screenBounds = [UIScreen mainScreen].bounds;
181     CGSize oldSize = webView.bounds.size;
182     mainWebView()->resizeTo(screenBounds.size.width, screenBounds.size.height, PlatformWebView::WebViewSizingMode::HeightRespectsStatusBar);
183     CGSize newSize = webView.bounds.size;
184     
185     if (!CGSizeEqualToSize(oldSize, newSize)) {
186         __block bool doneResizing = false;
187         [webView _doAfterNextVisibleContentRectUpdate: ^{
188             doneResizing = true;
189         }];
190
191         platformRunUntil(doneResizing, noTimeout);
192     }
193     
194     // We also pass data to InjectedBundle::beginTesting() to have it call
195     // WKBundlePageSetUseTestingViewportConfiguration(false).
196 }
197
198 void TestController::updatePlatformSpecificTestOptionsForTest(TestOptions& options, const std::string&) const
199 {
200     options.shouldShowTouches = shouldShowTouches();
201     [[GeneratedTouchesDebugWindow sharedGeneratedTouchesDebugWindow] setShouldShowTouches:options.shouldShowTouches];
202 }
203
204 void TestController::platformInitializeContext()
205 {
206 }
207
208 void TestController::runModal(PlatformWebView* view)
209 {
210     UIWindow *window = [view->platformView() window];
211     if (!window)
212         return;
213     // FIXME: how to perform on iOS?
214 //    [[UIApplication sharedApplication] runModalForWindow:window];
215 }
216
217 const char* TestController::platformLibraryPathForTesting()
218 {
219     return [[@"~/Library/Application Support/WebKitTestRunner" stringByExpandingTildeInPath] UTF8String];
220 }
221
222 void TestController::setHidden(bool)
223 {
224     // FIXME: implement for iOS
225 }
226
227 static UIKeyboardInputMode *swizzleCurrentInputMode()
228 {
229     return TestController::singleton().overriddenKeyboardInputMode();
230 }
231
232 static NSArray<UIKeyboardInputMode *> *swizzleActiveInputModes()
233 {
234     return @[ TestController::singleton().overriddenKeyboardInputMode() ];
235 }
236
237 void TestController::setKeyboardInputModeIdentifier(const String& identifier)
238 {
239     m_inputModeSwizzlers.clear();
240     m_overriddenKeyboardInputMode = [UIKeyboardInputMode keyboardInputModeWithIdentifier:identifier];
241     if (!m_overriddenKeyboardInputMode) {
242         ASSERT_NOT_REACHED();
243         return;
244     }
245
246     auto controllerClass = UIKeyboardInputModeController.class;
247     m_inputModeSwizzlers.reserveCapacity(3);
248     m_inputModeSwizzlers.uncheckedAppend(std::make_unique<InstanceMethodSwizzler>(controllerClass, @selector(currentInputMode), reinterpret_cast<IMP>(swizzleCurrentInputMode)));
249     m_inputModeSwizzlers.uncheckedAppend(std::make_unique<InstanceMethodSwizzler>(controllerClass, @selector(currentInputModeInPreference), reinterpret_cast<IMP>(swizzleCurrentInputMode)));
250     m_inputModeSwizzlers.uncheckedAppend(std::make_unique<InstanceMethodSwizzler>(controllerClass, @selector(activeInputModes), reinterpret_cast<IMP>(swizzleActiveInputModes)));
251     [UIKeyboardImpl.sharedInstance prepareKeyboardInputModeFromPreferences:nil];
252 }
253
254 } // namespace WTR