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