3a2a58da8954ea06163f8f27ec5735042f5fea25
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / WKWebViewSnapshot.mm
1 /*
2  * Copyright (C) 2016 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
28 #import "PlatformUtilities.h"
29 #import "Test.h"
30 #import "TestNavigationDelegate.h"
31 #import <WebKit/WKSnapshotConfiguration.h>
32 #import <wtf/RetainPtr.h>
33
34 static bool isDone;
35
36 #if PLATFORM(MAC)
37 typedef NSImage *PlatformImage;
38 typedef NSWindow *PlatformWindow;
39
40 static RetainPtr<CGImageRef> convertToCGImage(NSImage *image)
41 {
42     return [image CGImageForProposedRect:nil context:nil hints:nil];
43 }
44
45 #else
46 typedef UIImage *PlatformImage;
47 typedef UIWindow *PlatformWindow;
48
49 static RetainPtr<CGImageRef> convertToCGImage(UIImage *image)
50 {
51     return image.CGImage;
52 }
53 #endif
54
55 static NSInteger getPixelIndex(NSInteger x, NSInteger y, NSInteger width)
56 {
57     return (y * width + x) * 4;
58 }
59
60 TEST(WKWebView, SnapshotImageError)
61 {
62     CGFloat viewWidth = 800;
63     CGFloat viewHeight = 600;
64     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, viewWidth, viewHeight)]);
65     
66     [webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
67     [webView _test_waitForDidFinishNavigation];
68     [webView _killWebContentProcessAndResetState];
69
70     RetainPtr<WKSnapshotConfiguration> snapshotConfiguration = adoptNS([[WKSnapshotConfiguration alloc] init]);
71     [snapshotConfiguration setRect:NSMakeRect(0, 0, viewWidth, viewHeight)];
72     [snapshotConfiguration setSnapshotWidth:@(viewWidth)];
73
74     isDone = false;
75     [webView takeSnapshotWithConfiguration:snapshotConfiguration.get() completionHandler:^(PlatformImage snapshotImage, NSError *error) {
76         EXPECT_NULL(snapshotImage);
77         EXPECT_WK_STREQ(@"WKErrorDomain", error.domain);
78
79         isDone = true;
80     }];
81
82     TestWebKitAPI::Util::run(&isDone);
83 }
84
85 TEST(WKWebView, SnapshotImageBaseCase)
86 {
87     NSInteger viewWidth = 800;
88     NSInteger viewHeight = 600;
89     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, viewWidth, viewHeight)]);
90
91     RetainPtr<PlatformWindow> window;
92     CGFloat backingScaleFactor;
93
94 #if PLATFORM(MAC)
95     window = adoptNS([[NSWindow alloc] initWithContentRect:[webView frame] styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]);
96     [[window contentView] addSubview:webView.get()];
97     backingScaleFactor = [window backingScaleFactor];
98 #elif PLATFORM(IOS_FAMILY)
99     window = adoptNS([[UIWindow alloc] initWithFrame:[webView frame]]);
100     [window addSubview:webView.get()];
101     backingScaleFactor = [[window screen] scale];
102 #endif
103
104     [webView loadHTMLString:@"<body style='background-color:red;'><div style='background-color:blue; position:absolute; width:100px; height:100px; top:50px; left:50px'></div></body>" baseURL:nil];
105     [webView _test_waitForDidFinishNavigation];
106
107     RetainPtr<WKSnapshotConfiguration> snapshotConfiguration = adoptNS([[WKSnapshotConfiguration alloc] init]);
108     [snapshotConfiguration setRect:NSMakeRect(0, 0, viewWidth, viewHeight)];
109     [snapshotConfiguration setSnapshotWidth:@(viewWidth)];
110
111     isDone = false;
112     [webView takeSnapshotWithConfiguration:snapshotConfiguration.get() completionHandler:^(PlatformImage snapshotImage, NSError *error) {
113         EXPECT_NULL(error);
114
115         EXPECT_EQ(viewWidth, snapshotImage.size.width);
116
117         RetainPtr<CGImageRef> cgImage = convertToCGImage(snapshotImage);
118         RetainPtr<CGColorSpaceRef> colorSpace = adoptCF(CGColorSpaceCreateDeviceRGB());
119
120         NSInteger viewWidthInPixels = viewWidth * backingScaleFactor;
121         NSInteger viewHeightInPixels = viewHeight * backingScaleFactor;
122
123         unsigned char rgba[viewWidthInPixels * viewHeightInPixels * 4];
124         RetainPtr<CGContextRef> context = CGBitmapContextCreate(rgba, viewWidthInPixels, viewHeightInPixels, 8, 4 * viewWidthInPixels, colorSpace.get(), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
125         CGContextDrawImage(context.get(), CGRectMake(0, 0, viewWidthInPixels, viewHeightInPixels), cgImage.get());
126
127         NSInteger pixelIndex = getPixelIndex(0, 0, viewWidthInPixels);
128         EXPECT_EQ(255, rgba[pixelIndex]);
129         EXPECT_EQ(0, rgba[pixelIndex + 1]);
130         EXPECT_EQ(0, rgba[pixelIndex + 2]);
131
132         // Inside the blue div (50, 50, 100, 100)
133         pixelIndex = getPixelIndex(55 * backingScaleFactor, 55 * backingScaleFactor, viewWidthInPixels);
134         EXPECT_EQ(0, rgba[pixelIndex]);
135         EXPECT_EQ(0, rgba[pixelIndex + 1]);
136         EXPECT_EQ(255, rgba[pixelIndex + 2]);
137
138         pixelIndex = getPixelIndex(155 * backingScaleFactor, 155 * backingScaleFactor, viewWidthInPixels);
139         EXPECT_EQ(255, rgba[pixelIndex]);
140         EXPECT_EQ(0, rgba[pixelIndex + 1]);
141         EXPECT_EQ(0, rgba[pixelIndex + 2]);
142
143         isDone = true;
144     }];
145
146     TestWebKitAPI::Util::run(&isDone);
147 }
148
149 TEST(WKWebView, SnapshotImageScale)
150 {
151     CGFloat viewWidth = 800;
152     CGFloat viewHeight = 600;
153     CGFloat scaleFactor = 2;
154     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, viewWidth, viewHeight)]);
155
156     [webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
157     [webView _test_waitForDidFinishNavigation];
158
159     RetainPtr<WKSnapshotConfiguration> snapshotConfiguration = adoptNS([[WKSnapshotConfiguration alloc] init]);
160     [snapshotConfiguration setRect:NSMakeRect(0, 0, viewWidth, viewHeight)];
161     [snapshotConfiguration setSnapshotWidth:@(viewWidth * scaleFactor)];
162
163     isDone = false;
164     [webView takeSnapshotWithConfiguration:snapshotConfiguration.get() completionHandler:^(PlatformImage snapshotImage, NSError *error) {
165         EXPECT_NULL(error);
166         EXPECT_NOT_NULL(snapshotImage);
167         EXPECT_EQ(viewWidth * scaleFactor, snapshotImage.size.width);
168         EXPECT_EQ(viewHeight * scaleFactor, snapshotImage.size.height);
169
170         isDone = true;
171     }];
172
173     TestWebKitAPI::Util::run(&isDone);
174 }
175
176 TEST(WKWebView, SnapshotImageNilConfiguration)
177 {
178     CGFloat viewWidth = 800;
179     CGFloat viewHeight = 600;
180     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, viewWidth, viewHeight)]);
181
182     [webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
183     [webView _test_waitForDidFinishNavigation];
184
185     isDone = false;
186     [webView takeSnapshotWithConfiguration:nil completionHandler:^(PlatformImage snapshotImage, NSError *error) {
187         EXPECT_NULL(error);
188         EXPECT_NOT_NULL(snapshotImage);
189         EXPECT_EQ([webView bounds].size.width, snapshotImage.size.width);
190
191         isDone = true;
192     }];
193
194     TestWebKitAPI::Util::run(&isDone);
195 }
196
197 TEST(WKWebView, SnapshotImageUninitializedConfiguration)
198 {
199     CGFloat viewWidth = 800;
200     CGFloat viewHeight = 600;
201     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, viewWidth, viewHeight)]);
202
203     [webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
204     [webView _test_waitForDidFinishNavigation];
205
206     RetainPtr<WKSnapshotConfiguration> snapshotConfiguration = adoptNS([[WKSnapshotConfiguration alloc] init]);
207
208     isDone = false;
209     [webView takeSnapshotWithConfiguration:snapshotConfiguration.get() completionHandler:^(PlatformImage snapshotImage, NSError *error) {
210         EXPECT_NULL(error);
211         EXPECT_NOT_NULL(snapshotImage);
212         EXPECT_EQ([webView bounds].size.width, snapshotImage.size.width);
213
214         isDone = true;
215     }];
216
217     TestWebKitAPI::Util::run(&isDone);
218 }
219
220 TEST(WKWebView, SnapshotImageUninitializedSnapshotWidth)
221 {
222     CGFloat viewWidth = 800;
223     CGFloat viewHeight = 600;
224     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, viewWidth, viewHeight)]);
225
226     [webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
227     [webView _test_waitForDidFinishNavigation];
228
229     RetainPtr<WKSnapshotConfiguration> snapshotConfiguration = adoptNS([[WKSnapshotConfiguration alloc] init]);
230     [snapshotConfiguration setRect:NSMakeRect(0, 0, viewWidth, viewHeight)];
231
232     isDone = false;
233     [webView takeSnapshotWithConfiguration:snapshotConfiguration.get() completionHandler:^(PlatformImage snapshotImage, NSError *error) {
234         EXPECT_NULL(error);
235         EXPECT_NOT_NULL(snapshotImage);
236         EXPECT_EQ([snapshotConfiguration rect].size.width, snapshotImage.size.width);
237
238         isDone = true;
239     }];
240
241     TestWebKitAPI::Util::run(&isDone);
242 }
243
244 TEST(WKWebView, SnapshotImageLargeAsyncDecoding)
245 {
246     NSInteger viewWidth = 800;
247     NSInteger viewHeight = 600;
248     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, viewWidth, viewHeight)]);
249
250     NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"large-red-square-image" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
251     [webView loadFileURL:fileURL allowingReadAccessToURL:fileURL];
252     [webView _test_waitForDidFinishNavigation];
253
254     RetainPtr<WKSnapshotConfiguration> snapshotConfiguration = adoptNS([[WKSnapshotConfiguration alloc] init]);
255     [snapshotConfiguration setRect:NSMakeRect(0, 0, viewWidth, viewHeight)];
256     [snapshotConfiguration setSnapshotWidth:@(viewWidth)];
257
258     isDone = false;
259     [webView takeSnapshotWithConfiguration:snapshotConfiguration.get() completionHandler:^(PlatformImage snapshotImage, NSError *error) {
260         EXPECT_NULL(error);
261
262         EXPECT_EQ(viewWidth, snapshotImage.size.width);
263
264         RetainPtr<CGImageRef> cgImage = convertToCGImage(snapshotImage);
265         RetainPtr<CGColorSpaceRef> colorSpace = adoptCF(CGColorSpaceCreateDeviceRGB());
266
267         unsigned char rgba[viewWidth * viewHeight * 4];
268         RetainPtr<CGContextRef> context = CGBitmapContextCreate(rgba, viewWidth, viewHeight, 8, 4 * viewWidth, colorSpace.get(), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
269         CGContextDrawImage(context.get(), CGRectMake(0, 0, viewWidth, viewHeight), cgImage.get());
270
271         // Top-left corner of the div (0, 0, 100, 100)
272         NSInteger pixelIndex = getPixelIndex(0, 0, viewWidth);
273         EXPECT_EQ(255, rgba[pixelIndex]);
274         EXPECT_EQ(0, rgba[pixelIndex + 1]);
275         EXPECT_EQ(0, rgba[pixelIndex + 2]);
276
277         // Right-bottom corner of the div (0, 0, 100, 100)
278         pixelIndex = getPixelIndex(99, 99, viewWidth);
279         EXPECT_EQ(255, rgba[pixelIndex]);
280         EXPECT_EQ(0, rgba[pixelIndex + 1]);
281         EXPECT_EQ(0, rgba[pixelIndex + 2]);
282
283         // Outside the div (0, 0, 100, 100)
284         pixelIndex = getPixelIndex(100, 100, viewWidth);
285         EXPECT_EQ(255, rgba[pixelIndex]);
286         EXPECT_EQ(255, rgba[pixelIndex + 1]);
287         EXPECT_EQ(255, rgba[pixelIndex + 2]);
288
289         isDone = true;
290     }];
291
292     TestWebKitAPI::Util::run(&isDone);
293 }
294
295 TEST(WKWebView, SnapshotAfterScreenUpdates)
296 {
297     // The API tests currently cannot truly test SnapshotConfiguration.afterScreenUpdates since it is only needed
298     // on iOS devices, and we do not currently run API tests on iOS devices. So we expect this test to pass with
299     // afterScreenUpdates set to YES or NO on the configuration. On device, afterScreenUpdates must be YES in order
300     // pass this test.
301     NSInteger viewWidth = 800;
302     NSInteger viewHeight = 600;
303     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, viewWidth, viewHeight)]);
304     
305     RetainPtr<PlatformWindow> window;
306     CGFloat backingScaleFactor;
307     
308 #if PLATFORM(MAC)
309     window = adoptNS([[NSWindow alloc] initWithContentRect:[webView frame] styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]);
310     [[window contentView] addSubview:webView.get()];
311     backingScaleFactor = [window backingScaleFactor];
312 #elif PLATFORM(IOS_FAMILY)
313     window = adoptNS([[UIWindow alloc] initWithFrame:[webView frame]]);
314     [window addSubview:webView.get()];
315     backingScaleFactor = [[window screen] scale];
316 #endif
317     
318     [webView loadHTMLString:@"<body style='margin:0'><div id='change-me' style='background-color:red; position:fixed; width:100%; height:100%'></div></body>" baseURL:nil];
319     [webView _test_waitForDidFinishNavigation];
320     
321     RetainPtr<WKSnapshotConfiguration> snapshotConfiguration = adoptNS([[WKSnapshotConfiguration alloc] init]);
322     [snapshotConfiguration setRect:NSMakeRect(0, 0, viewWidth, viewHeight)];
323     [snapshotConfiguration setSnapshotWidth:@(viewWidth)];
324     [snapshotConfiguration setAfterScreenUpdates:YES];
325
326     [webView evaluateJavaScript:@"var div = document.getElementById('change-me');div.style.backgroundColor = 'blue';" completionHandler:nil];
327
328     isDone = false;
329     [webView takeSnapshotWithConfiguration:snapshotConfiguration.get() completionHandler:^(PlatformImage snapshotImage, NSError *error) {
330         EXPECT_NULL(error);
331         
332         EXPECT_EQ(viewWidth, snapshotImage.size.width);
333         
334         RetainPtr<CGImageRef> cgImage = convertToCGImage(snapshotImage);
335         RetainPtr<CGColorSpaceRef> colorSpace = adoptCF(CGColorSpaceCreateDeviceRGB());
336         
337         NSInteger viewWidthInPixels = viewWidth * backingScaleFactor;
338         NSInteger viewHeightInPixels = viewHeight * backingScaleFactor;
339         
340         unsigned char rgba[viewWidthInPixels * viewHeightInPixels * 4];
341         RetainPtr<CGContextRef> context = CGBitmapContextCreate(rgba, viewWidthInPixels, viewHeightInPixels, 8, 4 * viewWidthInPixels, colorSpace.get(), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
342         CGContextDrawImage(context.get(), CGRectMake(0, 0, viewWidthInPixels, viewHeightInPixels), cgImage.get());
343         
344         NSInteger pixelIndex = getPixelIndex(0, 0, viewWidthInPixels);
345         EXPECT_EQ(0, rgba[pixelIndex]);
346         EXPECT_EQ(0, rgba[pixelIndex + 1]);
347         EXPECT_EQ(255, rgba[pixelIndex + 2]);
348         
349         isDone = true;
350     }];
351     
352     TestWebKitAPI::Util::run(&isDone);
353 }
354
355 TEST(WKWebView, SnapshotWithoutAfterScreenUpdates)
356 {
357     // SnapshotConfiguration.afterScreenUpdates tests currently cannot truly test this API since it is only needed
358     // on iOS devices, and we do not currently run API tests on iOS devices. The expectations below are based on
359     // what we expect in the simulator and on macOS, which is that setting afterScreenUpdates to NO will still
360     // result in a snapshot that includes the recent screen updates. If we get these tests running on iOS device,
361     // then we would expect the pixels to be red instead of blue.
362     NSInteger viewWidth = 800;
363     NSInteger viewHeight = 600;
364     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, viewWidth, viewHeight)]);
365     
366     RetainPtr<PlatformWindow> window;
367     CGFloat backingScaleFactor;
368     
369 #if PLATFORM(MAC)
370     window = adoptNS([[NSWindow alloc] initWithContentRect:[webView frame] styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]);
371     [[window contentView] addSubview:webView.get()];
372     backingScaleFactor = [window backingScaleFactor];
373 #elif PLATFORM(IOS_FAMILY)
374     window = adoptNS([[UIWindow alloc] initWithFrame:[webView frame]]);
375     [window addSubview:webView.get()];
376     backingScaleFactor = [[window screen] scale];
377 #endif
378     
379     [webView loadHTMLString:@"<body style='margin:0'><div id='change-me' style='background-color:red; position:fixed; width:100%; height:100%'></div></body>" baseURL:nil];
380     [webView _test_waitForDidFinishNavigation];
381     
382     RetainPtr<WKSnapshotConfiguration> snapshotConfiguration = adoptNS([[WKSnapshotConfiguration alloc] init]);
383     [snapshotConfiguration setRect:NSMakeRect(0, 0, viewWidth, viewHeight)];
384     [snapshotConfiguration setSnapshotWidth:@(viewWidth)];
385     [snapshotConfiguration setAfterScreenUpdates:NO];
386     
387     [webView evaluateJavaScript:@"var div = document.getElementById('change-me');div.style.backgroundColor = 'blue';" completionHandler:nil];
388     
389     isDone = false;
390     [webView takeSnapshotWithConfiguration:snapshotConfiguration.get() completionHandler:^(PlatformImage snapshotImage, NSError *error) {
391         EXPECT_NULL(error);
392         
393         EXPECT_EQ(viewWidth, snapshotImage.size.width);
394         
395         RetainPtr<CGImageRef> cgImage = convertToCGImage(snapshotImage);
396         RetainPtr<CGColorSpaceRef> colorSpace = adoptCF(CGColorSpaceCreateDeviceRGB());
397         
398         NSInteger viewWidthInPixels = viewWidth * backingScaleFactor;
399         NSInteger viewHeightInPixels = viewHeight * backingScaleFactor;
400         
401         unsigned char rgba[viewWidthInPixels * viewHeightInPixels * 4];
402         RetainPtr<CGContextRef> context = CGBitmapContextCreate(rgba, viewWidthInPixels, viewHeightInPixels, 8, 4 * viewWidthInPixels, colorSpace.get(), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
403         CGContextDrawImage(context.get(), CGRectMake(0, 0, viewWidthInPixels, viewHeightInPixels), cgImage.get());
404         
405         NSInteger pixelIndex = getPixelIndex(0, 0, viewWidthInPixels);
406         EXPECT_EQ(0, rgba[pixelIndex]);
407         EXPECT_EQ(0, rgba[pixelIndex + 1]);
408         EXPECT_EQ(255, rgba[pixelIndex + 2]);
409         
410         isDone = true;
411     }];
412     
413     TestWebKitAPI::Util::run(&isDone);
414 }