Unreviewed iOS build fix after r230060.
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / ProcessSwapOnNavigation.mm
1 /*
2  * Copyright (C) 2017 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 <WebKit/WKNavigationDelegate.h>
31 #import <WebKit/WKPreferencesPrivate.h>
32 #import <WebKit/WKProcessPoolPrivate.h>
33 #import <WebKit/WKUIDelegatePrivate.h>
34 #import <WebKit/WKURLSchemeHandler.h>
35 #import <WebKit/WKURLSchemeTaskPrivate.h>
36 #import <WebKit/WKWebViewConfigurationPrivate.h>
37 #import <WebKit/WKWebViewPrivate.h>
38 #import <WebKit/WKWebsiteDataStorePrivate.h>
39 #import <WebKit/WKWebsiteDataStoreRef.h>
40 #import <WebKit/WebKit.h>
41 #import <WebKit/_WKExperimentalFeature.h>
42 #import <WebKit/_WKProcessPoolConfiguration.h>
43 #import <WebKit/_WKWebsiteDataStoreConfiguration.h>
44 #import <WebKit/_WKWebsitePolicies.h>
45 #import <wtf/Deque.h>
46 #import <wtf/HashMap.h>
47 #import <wtf/RetainPtr.h>
48 #import <wtf/Vector.h>
49 #import <wtf/text/StringHash.h>
50 #import <wtf/text/WTFString.h>
51
52 #if WK_API_ENABLED
53
54 static bool done;
55 static bool didCreateWebView;
56 static int numberOfDecidePolicyCalls;
57
58 static RetainPtr<NSMutableArray> receivedMessages = adoptNS([@[] mutableCopy]);
59 static bool receivedMessage;
60 @interface PSONMessageHandler : NSObject <WKScriptMessageHandler>
61 @end
62
63 @implementation PSONMessageHandler
64 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
65 {
66     [receivedMessages addObject:[message body]];
67     receivedMessage = true;
68 }
69 @end
70
71 @interface PSONNavigationDelegate : NSObject <WKNavigationDelegate>
72 @end
73
74 @implementation PSONNavigationDelegate
75
76 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
77 {
78     done = true;
79 }
80
81 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
82 {
83     ++numberOfDecidePolicyCalls;
84     decisionHandler(WKNavigationActionPolicyAllow);
85 }
86
87 @end
88
89 static RetainPtr<WKWebView> createdWebView;
90
91 @interface PSONUIDelegate : NSObject <WKUIDelegatePrivate>
92 - (instancetype)initWithNavigationDelegate:(PSONNavigationDelegate *)navigationDelegate;
93 @end
94
95 @implementation PSONUIDelegate {
96     RetainPtr<PSONNavigationDelegate> _navigationDelegate;
97 }
98
99 - (instancetype)initWithNavigationDelegate:(PSONNavigationDelegate *)navigationDelegate
100 {
101     if (!(self = [super init]))
102         return nil;
103
104     _navigationDelegate = navigationDelegate;
105     return self;
106 }
107
108 - (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
109 {
110     createdWebView = adoptNS([[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:configuration]);
111     [createdWebView setNavigationDelegate:_navigationDelegate.get()];
112     didCreateWebView = true;
113     return createdWebView.get();
114 }
115
116 @end
117
118 @interface PSONScheme : NSObject <WKURLSchemeHandler> {
119     const char* _bytes;
120 }
121 - (instancetype)initWithBytes:(const char*)bytes;
122 @end
123
124 @implementation PSONScheme
125
126 - (instancetype)initWithBytes:(const char*)bytes
127 {
128     self = [super init];
129     _bytes = bytes;
130     return self;
131 }
132
133 - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task
134 {
135     RetainPtr<NSURLResponse> response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:1 textEncodingName:nil]);
136     [task didReceiveResponse:response.get()];
137
138     if (_bytes) {
139         RetainPtr<NSData> data = adoptNS([[NSData alloc] initWithBytesNoCopy:(void *)_bytes length:strlen(_bytes) freeWhenDone:NO]);
140         [task didReceiveData:data.get()];
141     } else
142         [task didReceiveData:[@"Hello" dataUsingEncoding:NSUTF8StringEncoding]];
143
144     [task didFinish];
145 }
146
147 - (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task
148 {
149 }
150
151 @end
152
153 static const char* testBytes = R"PSONRESOURCE(
154 <head>
155 <script>
156
157 function log(msg)
158 {
159     window.webkit.messageHandlers.pson.postMessage(msg);
160 }
161
162 window.onload = function(evt) {
163     if (window.history.state != "onloadCalled")
164         setTimeout('window.history.replaceState("onloadCalled", "");', 0);
165 }
166
167 window.onpageshow = function(evt) {
168     log("PageShow called. Persisted: " + evt.persisted + ", and window.history.state is: " + window.history.state);
169 }
170
171 </script>
172 </head>
173 )PSONRESOURCE";
174
175 #if PLATFORM(MAC)
176
177 static const char* windowOpenCrossOriginNoOpenerTestBytes = R"PSONRESOURCE(
178 <script>
179 window.onload = function() {
180     window.open("pson2://host/main2.html", "_blank", "noopener");
181 }
182 </script>
183 )PSONRESOURCE";
184
185 static const char* windowOpenCrossOriginWithOpenerTestBytes = R"PSONRESOURCE(
186 <script>
187 window.onload = function() {
188     window.open("pson2://host/main2.html");
189 }
190 </script>
191 )PSONRESOURCE";
192
193 static const char* windowOpenSameOriginNoOpenerTestBytes = R"PSONRESOURCE(
194 <script>
195 window.onload = function() {
196     if (!opener)
197         window.open("pson1://host/main2.html", "_blank", "noopener");
198 }
199 </script>
200 )PSONRESOURCE";
201
202 static const char* dummyBytes = R"PSONRESOURCE(
203 <body>TEST</body>
204 )PSONRESOURCE";
205
206 #endif // PLATFORM(MAC)
207
208 TEST(ProcessSwap, Basic)
209 {
210     auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
211     processPoolConfiguration.get().processSwapsOnNavigation = YES;
212     auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
213
214     auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
215     [webViewConfiguration setProcessPool:processPool.get()];
216     RetainPtr<PSONScheme> handler = adoptNS([[PSONScheme alloc] init]);
217     [webViewConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"PSON1"];
218     [webViewConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"PSON2"];
219
220     auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
221     auto delegate = adoptNS([[PSONNavigationDelegate alloc] init]);
222     [webView setNavigationDelegate:delegate.get()];
223
224     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main1.html"]];
225     [webView loadRequest:request];
226
227     TestWebKitAPI::Util::run(&done);
228     done = false;
229
230     auto pid1 = [webView _webProcessIdentifier];
231
232     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main2.html"]];
233     [webView loadRequest:request];
234
235     TestWebKitAPI::Util::run(&done);
236     done = false;
237
238     auto pid2 = [webView _webProcessIdentifier];
239
240     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson2://host/main2.html"]];
241     [webView loadRequest:request];
242
243     TestWebKitAPI::Util::run(&done);
244     done = false;
245
246     auto pid3 = [webView _webProcessIdentifier];
247
248     EXPECT_EQ(pid1, pid2);
249     EXPECT_FALSE(pid2 == pid3);
250
251     // 3 loads, 3 decidePolicy calls (e.g. the load that did perform a process swap should not have generated an additional decidePolicy call)
252     EXPECT_EQ(numberOfDecidePolicyCalls, 3);
253 }
254
255 TEST(ProcessSwap, Back)
256 {
257     auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
258     processPoolConfiguration.get().processSwapsOnNavigation = YES;
259     auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
260
261     auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
262     [webViewConfiguration setProcessPool:processPool.get()];
263     RetainPtr<PSONScheme> handler1 = adoptNS([[PSONScheme alloc] initWithBytes:testBytes]);
264     RetainPtr<PSONScheme> handler2 = adoptNS([[PSONScheme alloc] init]);
265     [webViewConfiguration setURLSchemeHandler:handler1.get() forURLScheme:@"PSON1"];
266     [webViewConfiguration setURLSchemeHandler:handler2.get() forURLScheme:@"PSON2"];
267
268     RetainPtr<PSONMessageHandler> messageHandler = adoptNS([[PSONMessageHandler alloc] init]);
269     [[webViewConfiguration userContentController] addScriptMessageHandler:messageHandler.get() name:@"pson"];
270
271     auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
272     auto delegate = adoptNS([[PSONNavigationDelegate alloc] init]);
273     [webView setNavigationDelegate:delegate.get()];
274
275     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main1.html"]];
276     [webView loadRequest:request];
277
278     TestWebKitAPI::Util::run(&receivedMessage);
279     receivedMessage = false;
280     TestWebKitAPI::Util::run(&done);
281     done = false;
282
283     auto pid1 = [webView _webProcessIdentifier];
284
285     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson2://host/main2.html"]];
286     [webView loadRequest:request];
287
288     TestWebKitAPI::Util::run(&done);
289     done = false;
290
291     auto pid2 = [webView _webProcessIdentifier];
292
293     [webView goBack];
294
295     TestWebKitAPI::Util::run(&receivedMessage);
296     receivedMessage = false;
297     TestWebKitAPI::Util::run(&done);
298     done = false;
299
300     auto pid3 = [webView _webProcessIdentifier];
301
302     // 3 loads, 3 decidePolicy calls (e.g. any load that performs a process swap should not have generated an
303     // additional decidePolicy call as a result of the process swap)
304     EXPECT_EQ(numberOfDecidePolicyCalls, 3);
305
306     EXPECT_EQ([receivedMessages count], 2u);
307     EXPECT_TRUE([receivedMessages.get()[0] isEqualToString:@"PageShow called. Persisted: false, and window.history.state is: null"]);
308
309     // FIXME: We'd like to get the page restoring from the page cache like before process swapping, which will make Persisted be "true"
310     // For now it's expected to be false"
311     EXPECT_TRUE([receivedMessages.get()[1] isEqualToString:@"PageShow called. Persisted: false, and window.history.state is: onloadCalled"]);
312
313     EXPECT_FALSE(pid1 == pid2);
314     EXPECT_FALSE(pid2 == pid3);
315
316     // FIXME: Ideally we'd like to get process caching happening such that pid1 and pid3 are equal.
317     // But for now they should not be.
318     EXPECT_FALSE(pid1 == pid3);
319 }
320
321 #if PLATFORM(MAC)
322
323 TEST(ProcessSwap, CrossOriginWindowOpenNoOpener)
324 {
325     auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
326     processPoolConfiguration.get().processSwapsOnNavigation = YES;
327     auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
328
329     auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
330     [webViewConfiguration setProcessPool:processPool.get()];
331     RetainPtr<PSONScheme> handler1 = adoptNS([[PSONScheme alloc] initWithBytes:windowOpenCrossOriginNoOpenerTestBytes]);
332     RetainPtr<PSONScheme> handler2 = adoptNS([[PSONScheme alloc] initWithBytes:dummyBytes]);
333     [webViewConfiguration setURLSchemeHandler:handler1.get() forURLScheme:@"PSON1"];
334     [webViewConfiguration setURLSchemeHandler:handler2.get() forURLScheme:@"PSON2"];
335
336     auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
337     auto navigationDelegate = adoptNS([[PSONNavigationDelegate alloc] init]);
338     [webView setNavigationDelegate:navigationDelegate.get()];
339     auto uiDelegate = adoptNS([[PSONUIDelegate alloc] initWithNavigationDelegate:navigationDelegate.get()]);
340     [webView setUIDelegate:uiDelegate.get()];
341
342     numberOfDecidePolicyCalls = 0;
343     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main1.html"]];
344     [webView loadRequest:request];
345
346     TestWebKitAPI::Util::run(&done);
347     done = false;
348
349     TestWebKitAPI::Util::run(&didCreateWebView);
350     didCreateWebView = false;
351
352     TestWebKitAPI::Util::run(&done);
353
354     EXPECT_EQ(2, numberOfDecidePolicyCalls);
355
356     auto pid1 = [webView _webProcessIdentifier];
357     EXPECT_TRUE(!!pid1);
358     auto pid2 = [createdWebView _webProcessIdentifier];
359     EXPECT_TRUE(!!pid2);
360
361     EXPECT_NE(pid1, pid2);
362 }
363
364 TEST(ProcessSwap, CrossOriginWindowOpenWithOpener)
365 {
366     auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
367     processPoolConfiguration.get().processSwapsOnNavigation = YES;
368     auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
369
370     auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
371     [webViewConfiguration setProcessPool:processPool.get()];
372     RetainPtr<PSONScheme> handler1 = adoptNS([[PSONScheme alloc] initWithBytes:windowOpenCrossOriginWithOpenerTestBytes]);
373     RetainPtr<PSONScheme> handler2 = adoptNS([[PSONScheme alloc] initWithBytes:dummyBytes]);
374     [webViewConfiguration setURLSchemeHandler:handler1.get() forURLScheme:@"PSON1"];
375     [webViewConfiguration setURLSchemeHandler:handler2.get() forURLScheme:@"PSON2"];
376
377     auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
378     auto navigationDelegate = adoptNS([[PSONNavigationDelegate alloc] init]);
379     [webView setNavigationDelegate:navigationDelegate.get()];
380     auto uiDelegate = adoptNS([[PSONUIDelegate alloc] initWithNavigationDelegate:navigationDelegate.get()]);
381     [webView setUIDelegate:uiDelegate.get()];
382
383     numberOfDecidePolicyCalls = 0;
384     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main1.html"]];
385     [webView loadRequest:request];
386
387     TestWebKitAPI::Util::run(&done);
388     done = false;
389
390     TestWebKitAPI::Util::run(&didCreateWebView);
391     didCreateWebView = false;
392
393     TestWebKitAPI::Util::run(&done);
394
395     EXPECT_EQ(2, numberOfDecidePolicyCalls);
396
397     auto pid1 = [webView _webProcessIdentifier];
398     EXPECT_TRUE(!!pid1);
399     auto pid2 = [createdWebView _webProcessIdentifier];
400     EXPECT_TRUE(!!pid2);
401
402     // FIXME: This should eventually be false once we support process swapping when there is an opener.
403     EXPECT_EQ(pid1, pid2);
404 }
405
406 TEST(ProcessSwap, SameOriginWindowOpenNoOpener)
407 {
408     auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
409     processPoolConfiguration.get().processSwapsOnNavigation = YES;
410     auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
411
412     auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
413     [webViewConfiguration setProcessPool:processPool.get()];
414     RetainPtr<PSONScheme> handler = adoptNS([[PSONScheme alloc] initWithBytes:windowOpenSameOriginNoOpenerTestBytes]);
415     [webViewConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"PSON1"];
416
417     auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
418     auto navigationDelegate = adoptNS([[PSONNavigationDelegate alloc] init]);
419     [webView setNavigationDelegate:navigationDelegate.get()];
420     auto uiDelegate = adoptNS([[PSONUIDelegate alloc] initWithNavigationDelegate:navigationDelegate.get()]);
421     [webView setUIDelegate:uiDelegate.get()];
422
423     numberOfDecidePolicyCalls = 0;
424     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main1.html"]];
425     [webView loadRequest:request];
426
427     TestWebKitAPI::Util::run(&done);
428     done = false;
429
430     TestWebKitAPI::Util::run(&didCreateWebView);
431     didCreateWebView = false;
432
433     TestWebKitAPI::Util::run(&done);
434
435     EXPECT_EQ(2, numberOfDecidePolicyCalls);
436
437     auto pid1 = [webView _webProcessIdentifier];
438     EXPECT_TRUE(!!pid1);
439     auto pid2 = [createdWebView _webProcessIdentifier];
440     EXPECT_TRUE(!!pid2);
441
442     EXPECT_EQ(pid1, pid2);
443 }
444
445 #endif // PLATFORM(MAC)
446
447 #endif // WK_API_ENABLED