ac9ae01006d92af1c40f0aa1fcb33faf872547f2
[WebKit.git] / Tools / WebKitTestRunner / ios / PlatformWebViewIOS.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 "PlatformWebView.h"
28
29 #import "TestController.h"
30 #import "TestRunnerWKWebView.h"
31 #import "UIKitTestSPI.h"
32 #import <WebKit/WKImageCG.h>
33 #import <WebKit/WKPreferencesPrivate.h>
34 #import <WebKit/WKSnapshotConfiguration.h>
35 #import <WebKit/WKWebViewConfiguration.h>
36 #import <WebKit/WKWebViewPrivate.h>
37 #import <pal/spi/cocoa/QuartzCoreSPI.h>
38 #import <wtf/BlockObjCExceptions.h>
39 #import <wtf/RetainPtr.h>
40
41 @interface WKWebView (Details)
42 - (WKPageRef)_pageForTesting;
43 @end
44
45 @interface WebKitTestRunnerWindow : UIWindow {
46     WTR::PlatformWebView* _platformWebView;
47     CGPoint _fakeOrigin;
48     BOOL _initialized;
49 }
50 @property (nonatomic, assign) WTR::PlatformWebView* platformWebView;
51 @end
52
53 static Vector<WebKitTestRunnerWindow*> allWindows;
54
55 @implementation WebKitTestRunnerWindow
56 @synthesize platformWebView = _platformWebView;
57
58 - (id)initWithFrame:(CGRect)frame
59 {
60     allWindows.append(self);
61
62     if ((self = [super initWithFrame:frame]))
63         _initialized = YES;
64
65     return self;
66 }
67
68 - (void)dealloc
69 {
70     allWindows.removeFirst(self);
71     ASSERT(!allWindows.contains(self));
72     [super dealloc];
73 }
74
75 - (BOOL)isKeyWindow
76 {
77     return [super isKeyWindow] && (_platformWebView ? _platformWebView->windowIsKey() : YES);
78 }
79
80 - (void)setFrameOrigin:(CGPoint)point
81 {
82     _fakeOrigin = point;
83 }
84
85 - (void)setFrame:(CGRect)windowFrame
86 {
87     if (!_initialized) {
88         [super setFrame:windowFrame];
89         return;
90     }
91
92     CGRect currentFrame = [super frame];
93
94     _fakeOrigin = windowFrame.origin;
95
96     [super setFrame:CGRectMake(currentFrame.origin.x, currentFrame.origin.y, windowFrame.size.width, windowFrame.size.height)];
97 }
98
99 - (CGRect)frameRespectingFakeOrigin
100 {
101     CGRect currentFrame = [self frame];
102     return CGRectMake(_fakeOrigin.x, _fakeOrigin.y, currentFrame.size.width, currentFrame.size.height);
103 }
104
105 - (CGFloat)backingScaleFactor
106 {
107     return 1;
108 }
109
110 @end
111
112 namespace WTR {
113
114 enum class WebViewSizingMode {
115     Default,
116     HeightRespectsStatusBar
117 };
118
119 static CGRect viewRectForWindowRect(CGRect, PlatformWebView::WebViewSizingMode);
120
121 } // namespace WTR
122
123 @interface PlatformWebViewController : UIViewController
124 @end
125
126 @implementation PlatformWebViewController
127
128 - (void)viewWillTransitionToSize:(CGSize)toSize withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
129 {
130     [super viewWillTransitionToSize:toSize withTransitionCoordinator:coordinator];
131
132     TestRunnerWKWebView *webView = WTR::TestController::singleton().mainWebView()->platformView();
133
134     if (webView.usesSafariLikeRotation)
135         [webView _setInterfaceOrientationOverride:[[UIApplication sharedApplication] statusBarOrientation]];
136
137     [coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
138         // This code assumes that we should take the status bar into account, which we only do for flexible viewport tests,
139         // but it only makes sense to test rotation with a flexible viewport anyway.
140         if (webView.usesSafariLikeRotation) {
141             [webView _beginAnimatedResizeWithUpdates:^{
142                 webView.frame = viewRectForWindowRect(self.view.bounds, WTR::PlatformWebView::WebViewSizingMode::HeightRespectsStatusBar);
143                 [webView _overrideLayoutParametersWithMinimumLayoutSize:webView.frame.size maximumUnobscuredSizeOverride:webView.frame.size];
144                 [webView _setInterfaceOrientationOverride:[[UIApplication sharedApplication] statusBarOrientation]];
145             }];
146         } else
147             webView.frame = viewRectForWindowRect(self.view.bounds, WTR::PlatformWebView::WebViewSizingMode::HeightRespectsStatusBar);
148     } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
149         webView.frame = viewRectForWindowRect(self.view.bounds, WTR::PlatformWebView::WebViewSizingMode::HeightRespectsStatusBar);
150         if (webView.usesSafariLikeRotation)
151             [webView _endAnimatedResize];
152
153         [webView _didEndRotation];
154     }];
155 }
156
157 @end
158
159 namespace WTR {
160
161 static CGRect viewRectForWindowRect(CGRect windowRect, PlatformWebView::WebViewSizingMode mode)
162 {
163     CGFloat statusBarBottom = CGRectGetMaxY([[UIApplication sharedApplication] statusBarFrame]);
164     return CGRectMake(windowRect.origin.x, windowRect.origin.y + statusBarBottom, windowRect.size.width, windowRect.size.height - (mode == PlatformWebView::WebViewSizingMode::HeightRespectsStatusBar ? statusBarBottom : 0));
165 }
166
167 PlatformWebView::PlatformWebView(WKWebViewConfiguration* configuration, const TestOptions& options)
168     : m_windowIsKey(true)
169     , m_options(options)
170 {
171     CGRect rect = CGRectMake(0, 0, TestController::viewWidth, TestController::viewHeight);
172
173     m_window = [[WebKitTestRunnerWindow alloc] initWithFrame:rect];
174     m_window.backgroundColor = [UIColor lightGrayColor];
175     m_window.platformWebView = this;
176
177     UIViewController *viewController = [[PlatformWebViewController alloc] init];
178     [m_window setRootViewController:viewController];
179     [viewController release];
180
181     m_view = [[TestRunnerWKWebView alloc] initWithFrame:viewRectForWindowRect(rect, WebViewSizingMode::Default) configuration:configuration];
182
183     [m_window.rootViewController.view addSubview:m_view];
184     [m_window makeKeyAndVisible];
185 }
186
187 PlatformWebView::~PlatformWebView()
188 {
189     m_window.platformWebView = nil;
190     [m_view release];
191     [m_window release];
192 }
193
194 PlatformWindow PlatformWebView::keyWindow()
195 {
196     size_t i = allWindows.size();
197     while (i) {
198         if ([allWindows[i] isKeyWindow])
199             return allWindows[i];
200         --i;
201     }
202
203     return nil;
204 }
205
206 void PlatformWebView::setWindowIsKey(bool isKey)
207 {
208     m_windowIsKey = isKey;
209     if (isKey)
210         [m_window makeKeyWindow];
211 }
212
213 void PlatformWebView::addToWindow()
214 {
215     [m_window.rootViewController.view addSubview:m_view];
216 }
217
218 void PlatformWebView::removeFromWindow()
219 {
220     [m_view removeFromSuperview];
221 }
222
223 void PlatformWebView::resizeTo(unsigned width, unsigned height, WebViewSizingMode viewSizingMode)
224 {
225     WKRect frame = windowFrame();
226     frame.size.width = width;
227     frame.size.height = height;
228     setWindowFrame(frame, viewSizingMode);
229 }
230
231 WKPageRef PlatformWebView::page()
232 {
233     return [m_view _pageForTesting];
234 }
235
236 void PlatformWebView::focus()
237 {
238     makeWebViewFirstResponder();
239     setWindowIsKey(true);
240 }
241
242 WKRect PlatformWebView::windowFrame()
243 {
244     CGRect frame = [m_window frameRespectingFakeOrigin];
245
246     WKRect wkFrame;
247     wkFrame.origin.x = frame.origin.x;
248     wkFrame.origin.y = frame.origin.y;
249     wkFrame.size.width = frame.size.width;
250     wkFrame.size.height = frame.size.height;
251     return wkFrame;
252 }
253
254 void PlatformWebView::setWindowFrame(WKRect frame, WebViewSizingMode viewSizingMode)
255 {
256     [m_window setFrame:CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height)];
257     [platformView() setFrame:viewRectForWindowRect(CGRectMake(0, 0, frame.size.width, frame.size.height), viewSizingMode)];
258 }
259
260 void PlatformWebView::didInitializeClients()
261 {
262     // Set a temporary 1x1 window frame to force a WindowAndViewFramesChanged notification. <rdar://problem/13380145>
263     WKRect wkFrame = windowFrame();
264     [m_window setFrame:CGRectMake(0, 0, 1, 1)];
265     setWindowFrame(wkFrame);
266 }
267
268 void PlatformWebView::addChromeInputField()
269 {
270     UITextField* textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 20)];
271     textField.tag = 1;
272     [m_window addSubview:textField];
273     [textField release];
274 }
275
276 void PlatformWebView::removeChromeInputField()
277 {
278     UITextField* textField = (UITextField*)[m_window viewWithTag:1];
279     if (textField) {
280         [textField removeFromSuperview];
281         makeWebViewFirstResponder();
282         [textField release];
283     }
284 }
285
286 void PlatformWebView::makeWebViewFirstResponder()
287 {
288     // FIXME: iOS equivalent?
289     // [m_window makeFirstResponder:m_view];
290 }
291
292 void PlatformWebView::changeWindowScaleIfNeeded(float)
293 {
294     // Retina only surface.
295 }
296
297 bool PlatformWebView::drawsBackground() const
298 {
299     return false;
300 }
301
302 void PlatformWebView::setDrawsBackground(bool)
303 {
304 }
305
306 #if !HAVE(IOSURFACE)
307 static void releaseDataProviderData(void* info, const void*, size_t)
308 {
309     CARenderServerDestroyBuffer(static_cast<CARenderServerBufferRef>(info));
310 }
311 #endif
312
313 RetainPtr<CGImageRef> PlatformWebView::windowSnapshotImage()
314 {
315     BEGIN_BLOCK_OBJC_EXCEPTIONS;
316 #if HAVE(IOSURFACE)
317     __block bool isDone = false;
318     __block RetainPtr<CGImageRef> result;
319     
320     RetainPtr<WKSnapshotConfiguration> snapshotConfiguration = adoptNS([[WKSnapshotConfiguration alloc] init]);
321     [snapshotConfiguration setRect:CGRectMake(0, 0, m_view.frame.size.width, m_view.frame.size.height)];
322     [snapshotConfiguration setSnapshotWidth:@(m_view.frame.size.width)];
323     
324     [m_view takeSnapshotWithConfiguration:snapshotConfiguration.get() completionHandler:^(UIImage *snapshotImage, NSError *error) {
325         if (!error)
326             result = [snapshotImage CGImage];
327         isDone = true;
328     }];
329     while (!isDone)
330         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
331     return result;
332
333 #else
334     CGFloat deviceScaleFactor = 2; // FIXME: hardcode 2x for now. In future we could respect 1x and 3x as we do on Mac.
335     CATransform3D transform = CATransform3DMakeScale(deviceScaleFactor, deviceScaleFactor, 1);
336     
337     CGSize viewSize = m_view.bounds.size;
338     int bufferWidth = ceil(viewSize.width * deviceScaleFactor);
339     int bufferHeight = ceil(viewSize.height * deviceScaleFactor);
340     if (!bufferWidth || !bufferHeight) {
341         WTFLogAlways("Being asked for snapshot of view with width %d height %d\n", bufferWidth, bufferHeight);
342         return nullptr;
343     }
344
345     CARenderServerBufferRef buffer = CARenderServerCreateBuffer(bufferWidth, bufferHeight);
346     if (!buffer) {
347         WTFLogAlways("CARenderServerCreateBuffer failed for buffer with width %d height %d\n", bufferWidth, bufferHeight);
348         return nullptr;
349     }
350
351     CARenderServerRenderLayerWithTransform(MACH_PORT_NULL, m_view.layer.context.contextId, reinterpret_cast<uint64_t>(m_view.layer), buffer, 0, 0, &transform);
352
353     uint8_t* data = CARenderServerGetBufferData(buffer);
354     size_t rowBytes = CARenderServerGetBufferRowBytes(buffer);
355
356     static CGColorSpaceRef sRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
357     RetainPtr<CGDataProviderRef> provider = adoptCF(CGDataProviderCreateWithData(buffer, data, CARenderServerGetBufferDataSize(buffer), releaseDataProviderData));
358     
359     RetainPtr<CGImageRef> cgImage = adoptCF(CGImageCreate(bufferWidth, bufferHeight, 8, 32, rowBytes, sRGBSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, provider.get(), 0, false, kCGRenderingIntentDefault));
360
361     return cgImage;
362 #endif
363     END_BLOCK_OBJC_EXCEPTIONS;
364 }
365
366 void PlatformWebView::setNavigationGesturesEnabled(bool enabled)
367 {
368 #if WK_API_ENABLED
369     [platformView() setAllowsBackForwardNavigationGestures:enabled];
370 #endif
371 }
372
373 } // namespace WTR