ServiceWorkerClientFetch should send data to its resource loader once the didReceiveR...
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / ServiceWorkerBasic.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/WKPreferencesPrivate.h>
31 #import <WebKit/WKProcessPoolPrivate.h>
32 #import <WebKit/WKURLSchemeHandler.h>
33 #import <WebKit/WKURLSchemeTaskPrivate.h>
34 #import <WebKit/WKWebViewConfigurationPrivate.h>
35 #import <WebKit/WKWebsiteDataStorePrivate.h>
36 #import <WebKit/WKWebsiteDataStoreRef.h>
37 #import <WebKit/WebKit.h>
38 #import <WebKit/_WKExperimentalFeature.h>
39 #import <WebKit/_WKWebsiteDataStoreConfiguration.h>
40 #import <wtf/Deque.h>
41 #import <wtf/HashMap.h>
42 #import <wtf/RetainPtr.h>
43 #import <wtf/Vector.h>
44 #import <wtf/text/StringHash.h>
45 #import <wtf/text/WTFString.h>
46
47 #if WK_API_ENABLED
48
49 struct ResourceInfo {
50     RetainPtr<NSString> mimeType;
51     const char* data;
52 };
53
54 static bool done;
55
56 static String expectedMessage;
57 static String retrievedString;
58
59 @interface SWMessageHandler : NSObject <WKScriptMessageHandler>
60 @end
61
62 @implementation SWMessageHandler
63 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
64 {
65     EXPECT_TRUE([[message body] isEqualToString:@"Message from worker: ServiceWorker received: Hello from the web page"]);
66     done = true;
67 }
68 @end
69
70 @interface SWMessageHandlerForFetchTest : NSObject <WKScriptMessageHandler>
71 @end
72
73 @implementation SWMessageHandlerForFetchTest
74 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
75 {
76     EXPECT_TRUE([[message body] isEqualToString:@"Intercepted by worker"]);
77     done = true;
78 }
79 @end
80
81 @interface SWMessageHandlerForRestoreFromDiskTest : NSObject <WKScriptMessageHandler> {
82     NSString *_expectedMessage;
83 }
84 - (instancetype)initWithExpectedMessage:(NSString *)expectedMessage;
85 @end
86
87 @interface SWMessageHandlerWithExpectedMessage : NSObject <WKScriptMessageHandler>
88 @end
89
90 @implementation SWMessageHandlerWithExpectedMessage
91 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
92 {
93     EXPECT_TRUE([[message body] isEqualToString:expectedMessage]);
94     done = true;
95 }
96 @end
97
98 @implementation SWMessageHandlerForRestoreFromDiskTest
99
100 - (instancetype)initWithExpectedMessage:(NSString *)expectedMessage
101 {
102     if (!(self = [super init]))
103         return nil;
104
105     _expectedMessage = expectedMessage;
106
107     return self;
108 }
109
110 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
111 {
112     EXPECT_TRUE([[message body] isEqualToString:_expectedMessage]);
113     done = true;
114 }
115 @end
116
117 @interface SWSchemes : NSObject <WKURLSchemeHandler> {
118 @public
119     HashMap<String, ResourceInfo> resources;
120 }
121
122 -(size_t)handledRequests;
123 @end
124
125 @implementation SWSchemes {
126     size_t _handledRequests;
127 }
128
129 -(size_t)handledRequests
130 {
131     return _handledRequests;
132 }
133
134 - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task
135 {
136     auto entry = resources.find([task.request.URL absoluteString]);
137     if (entry == resources.end()) {
138         NSLog(@"Did not find resource entry for URL %@", task.request.URL);
139         return;
140     }
141
142     ++_handledRequests;
143     RetainPtr<NSURLResponse> response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:entry->value.mimeType.get() expectedContentLength:1 textEncodingName:nil]);
144     [task didReceiveResponse:response.get()];
145
146     [task didReceiveData:[NSData dataWithBytesNoCopy:(void*)entry->value.data length:strlen(entry->value.data) freeWhenDone:NO]];
147     [task didFinish];
148 }
149
150 - (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task
151 {
152 }
153
154 @end
155
156 static bool shouldAccept = true;
157 static bool navigationComplete = false;
158 static bool navigationFailed = false;
159
160 @interface TestSWAsyncNavigationDelegate : NSObject <WKNavigationDelegate, WKUIDelegate>
161 @end
162
163 @implementation TestSWAsyncNavigationDelegate
164
165 - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
166 {
167     navigationComplete = true;
168 }
169
170 - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
171 {
172     navigationFailed = true;
173     navigationComplete = true;
174 }
175
176 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
177 {
178     navigationFailed = true;
179     navigationComplete = true;
180 }
181
182 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
183 {
184     decisionHandler(WKNavigationActionPolicyAllow);
185 }
186
187 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
188 {
189     int64_t deferredWaitTime = 100 * NSEC_PER_MSEC;
190     dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, deferredWaitTime);
191     dispatch_after(when, dispatch_get_main_queue(), ^{
192         decisionHandler(shouldAccept ? WKNavigationResponsePolicyAllow : WKNavigationResponsePolicyCancel);
193     });
194 }
195 @end
196
197
198 static const char* mainBytes = R"SWRESOURCE(
199 <script>
200
201 function log(msg)
202 {
203     window.webkit.messageHandlers.sw.postMessage(msg);
204 }
205
206 navigator.serviceWorker.addEventListener("message", function(event) {
207     log("Message from worker: " + event.data);
208 });
209
210 try {
211
212 navigator.serviceWorker.register('/sw.js').then(function(reg) {
213     reg.installing.postMessage("Hello from the web page");
214 }).catch(function(error) {
215     log("Registration failed with: " + error);
216 });
217 } catch(e) {
218     log("Exception: " + e);
219 }
220
221 </script>
222 )SWRESOURCE";
223
224 static const char* scriptBytes = R"SWRESOURCE(
225
226 self.addEventListener("message", (event) => {
227     event.source.postMessage("ServiceWorker received: " + event.data);
228 });
229
230 )SWRESOURCE";
231
232 static const char* mainForFetchTestBytes = R"SWRESOURCE(
233 <html>
234 <body>
235 <script>
236 function log(msg)
237 {
238     window.webkit.messageHandlers.sw.postMessage(msg);
239 }
240
241 try {
242
243 function addFrame()
244 {
245     frame = document.createElement('iframe');
246     frame.src = "/test.html";
247     frame.onload = function() { window.webkit.messageHandlers.sw.postMessage(frame.contentDocument.body.innerHTML); }
248     document.body.appendChild(frame);
249 }
250
251 navigator.serviceWorker.register('/sw.js').then(function(reg) {
252     if (reg.active) {
253         addFrame();
254         return;
255     }
256     worker = reg.installing;
257     worker.addEventListener('statechange', function() {
258         if (worker.state == 'activated')
259             addFrame();
260     });
261 }).catch(function(error) {
262     log("Registration failed with: " + error);
263 });
264 } catch(e) {
265     log("Exception: " + e);
266 }
267
268 </script>
269 </body>
270 </html>
271 )SWRESOURCE";
272
273 static const char* scriptHandlingFetchBytes = R"SWRESOURCE(
274
275 self.addEventListener("fetch", (event) => {
276     if (event.request.url.indexOf("test.html") !== -1) {
277         event.respondWith(new Response(new Blob(['Intercepted by worker'], {type: 'text/html'})));
278     }
279 });
280
281 )SWRESOURCE";
282
283 static const char* scriptInterceptingFirstLoadBytes = R"SWRESOURCE(
284
285 self.addEventListener("fetch", (event) => {
286     if (event.request.url.indexOf("main.html") !== -1) {
287         event.respondWith(new Response(new Blob(['Intercepted by worker <script>window.webkit.messageHandlers.sw.postMessage(\'Intercepted by worker\');</script>'], {type: 'text/html'})));
288     }
289 });
290
291 )SWRESOURCE";
292
293 static const char* mainForFirstLoadInterceptTestBytes = R"SWRESOURCE(
294  <html>
295 <body>
296 <script>
297 function log(msg)
298 {
299     window.webkit.messageHandlers.sw.postMessage(msg);
300 }
301
302 try {
303
304 navigator.serviceWorker.register('/sw.js').then(function(reg) {
305     if (reg.active) {
306         window.webkit.messageHandlers.sw.postMessage('Service Worker activated');
307         return;
308     }
309
310     worker = reg.installing;
311     worker.addEventListener('statechange', function() {
312         if (worker.state == 'activated')
313             window.webkit.messageHandlers.sw.postMessage('Service Worker activated');
314     });
315 }).catch(function(error) {
316     log("Registration failed with: " + error);
317 });
318 } catch(e) {
319     log("Exception: " + e);
320 }
321
322 </script>
323 </body>
324 </html>
325 )SWRESOURCE";
326
327 static const char* testBytes = R"SWRESOURCE(
328 <body>
329 NOT intercepted by worker
330 </body>
331 )SWRESOURCE";
332
333 static const char* mainRegisteringWorkerBytes = R"SWRESOURCE(
334 <script>
335 try {
336 function log(msg)
337 {
338     window.webkit.messageHandlers.sw.postMessage(msg);
339 }
340
341 navigator.serviceWorker.register('/sw.js').then(function(reg) {
342     if (reg.active) {
343         log("FAIL: Registration already has an active worker");
344         return;
345     }
346     worker = reg.installing;
347     worker.addEventListener('statechange', function() {
348         if (worker.state == 'activated')
349             log("PASS: Registration was successful and service worker was activated");
350     });
351 }).catch(function(error) {
352     log("Registration failed with: " + error);
353 });
354 } catch(e) {
355     log("Exception: " + e);
356 }
357 </script>
358 )SWRESOURCE";
359
360 static const char* mainRegisteringAlreadyExistingWorkerBytes = R"SWRESOURCE(
361 <script>
362 try {
363 function log(msg)
364 {
365     window.webkit.messageHandlers.sw.postMessage(msg);
366 }
367
368 navigator.serviceWorker.register('/sw.js').then(function(reg) {
369     if (reg.installing) {
370         log("FAIL: Registration had an installing worker");
371         return;
372     }
373     if (reg.active) {
374         if (reg.active.state == "activated")
375             log("PASS: Registration already has an active worker");
376         else
377             log("FAIL: Registration has an active worker but its state is not activated");
378     } else
379         log("FAIL: Registration does not have an active worker");
380 }).catch(function(error) {
381     log("Registration failed with: " + error);
382 });
383 } catch(e) {
384     log("Exception: " + e);
385 }
386 </script>
387 )SWRESOURCE";
388
389 static const char* mainBytesForSessionIDTest = R"SWRESOURCE(
390 <script>
391
392 function log(msg)
393 {
394     window.webkit.messageHandlers.sw.postMessage(msg);
395 }
396
397 navigator.serviceWorker.addEventListener("message", function(event) {
398     log(event.data);
399 });
400
401 try {
402
403 navigator.serviceWorker.register('/sw.js').then(function(reg) {
404     worker = reg.installing;
405     worker.addEventListener('statechange', function() {
406         if (worker.state == 'activated')
407             worker.postMessage("TEST");
408     });
409 }).catch(function(error) {
410     log("Registration failed with: " + error);
411 });
412 } catch(e) {
413     log("Exception: " + e);
414 }
415
416 </script>
417 )SWRESOURCE";
418
419 static const char* scriptBytesForSessionIDTest = R"SWRESOURCE(
420
421 var wasActivated = false;
422
423 self.addEventListener("activate", event => {
424     event.waitUntil(clients.claim().then( () => {
425         wasActivated = true;
426     }));
427 });
428
429 self.addEventListener("message", (event) => {
430     if (wasActivated && registration.active)
431         event.source.postMessage("PASS: activation successful");
432     else
433         event.source.postMessage("FAIL: failed to activate");
434 });
435
436 )SWRESOURCE";
437
438 TEST(ServiceWorkers, Basic)
439 {
440     ASSERT(mainBytes);
441     ASSERT(scriptBytes);
442
443     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
444
445     // Start with a clean slate data store
446     [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
447         done = true;
448     }];
449     TestWebKitAPI::Util::run(&done);
450     done = false;
451
452     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
453
454     RetainPtr<SWMessageHandler> messageHandler = adoptNS([[SWMessageHandler alloc] init]);
455     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
456
457     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
458     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainBytes });
459     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptBytes });
460     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
461
462     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
463     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
464
465     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
466     [webView loadRequest:request];
467
468     TestWebKitAPI::Util::run(&done);
469     done = false;
470
471     webView = nullptr;
472
473     [[WKWebsiteDataStore defaultDataStore] fetchDataRecordsOfTypes:[NSSet setWithObject:WKWebsiteDataTypeServiceWorkerRegistrations] completionHandler:^(NSArray<WKWebsiteDataRecord *> *websiteDataRecords) {
474         EXPECT_EQ(1u, [websiteDataRecords count]);
475         EXPECT_TRUE([websiteDataRecords[0].displayName isEqualToString:@"sw host"]);
476
477         done = true;
478     }];
479
480     TestWebKitAPI::Util::run(&done);
481     done = false;
482 }
483
484 TEST(ServiceWorkers, RestoreFromDisk)
485 {
486     ASSERT(mainRegisteringWorkerBytes);
487     ASSERT(scriptBytes);
488     ASSERT(mainRegisteringAlreadyExistingWorkerBytes);
489
490     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
491
492     // Start with a clean slate data store
493     [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
494         done = true;
495     }];
496     TestWebKitAPI::Util::run(&done);
497     done = false;
498
499     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
500
501     RetainPtr<SWMessageHandlerForRestoreFromDiskTest> messageHandler = adoptNS([[SWMessageHandlerForRestoreFromDiskTest alloc] initWithExpectedMessage:@"PASS: Registration was successful and service worker was activated"]);
502     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
503
504     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
505     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainRegisteringWorkerBytes });
506     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptBytes });
507     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
508
509     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
510     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
511
512     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
513     [webView loadRequest:request];
514
515     TestWebKitAPI::Util::run(&done);
516
517     webView = nullptr;
518     configuration = nullptr;
519     messageHandler = nullptr;
520     handler = nullptr;
521
522     done = false;
523
524     configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
525     messageHandler = adoptNS([[SWMessageHandlerForRestoreFromDiskTest alloc] initWithExpectedMessage:@"PASS: Registration already has an active worker"]);
526     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
527
528     handler = adoptNS([[SWSchemes alloc] init]);
529     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainRegisteringAlreadyExistingWorkerBytes });
530     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptBytes });
531     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
532
533     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
534     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
535
536     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
537     [webView loadRequest:request];
538
539     TestWebKitAPI::Util::run(&done);
540     done = false;
541 }
542
543 TEST(ServiceWorkers, FetchAfterRestoreFromDisk)
544 {
545     ASSERT(mainForFetchTestBytes);
546     ASSERT(scriptHandlingFetchBytes);
547
548     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
549
550     // Start with a clean slate data store
551     [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
552         done = true;
553     }];
554     TestWebKitAPI::Util::run(&done);
555     done = false;
556
557     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
558
559     RetainPtr<SWMessageHandlerForFetchTest> messageHandler = adoptNS([[SWMessageHandlerForFetchTest alloc] init]);
560     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
561
562     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
563     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainForFetchTestBytes });
564     handler->resources.set("sw://host/test.html", ResourceInfo { @"text/html", testBytes });
565     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptHandlingFetchBytes });
566     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
567
568     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
569     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
570
571     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
572     [webView loadRequest:request];
573
574     TestWebKitAPI::Util::run(&done);
575
576     webView = nullptr;
577     configuration = nullptr;
578     messageHandler = nullptr;
579     handler = nullptr;
580
581     done = false;
582
583     configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
584     messageHandler = adoptNS([[SWMessageHandlerForFetchTest alloc] init]);
585     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
586
587     handler = adoptNS([[SWSchemes alloc] init]);
588     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainForFetchTestBytes });
589     handler->resources.set("sw://host/test.html", ResourceInfo { @"text/html", testBytes });
590     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptHandlingFetchBytes });
591     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
592
593     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
594     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
595
596     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
597     [webView loadRequest:request];
598
599     TestWebKitAPI::Util::run(&done);
600     done = false;
601 }
602
603 TEST(ServiceWorkers, InterceptFirstLoadAfterRestoreFromDisk)
604 {
605     ASSERT(mainForFirstLoadInterceptTestBytes);
606     ASSERT(scriptHandlingFetchBytes);
607
608     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
609
610     // Start with a clean slate data store
611     [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
612         done = true;
613     }];
614     TestWebKitAPI::Util::run(&done);
615     done = false;
616
617     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
618
619     RetainPtr<SWMessageHandlerWithExpectedMessage> messageHandler = adoptNS([[SWMessageHandlerWithExpectedMessage alloc] init]);
620     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
621
622     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
623     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainForFirstLoadInterceptTestBytes });
624     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptInterceptingFirstLoadBytes });
625     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
626
627     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
628     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
629
630     expectedMessage = "Service Worker activated";
631     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
632     [webView loadRequest:request];
633
634     TestWebKitAPI::Util::run(&done);
635
636     webView = nullptr;
637     configuration = nullptr;
638     messageHandler = nullptr;
639     handler = nullptr;
640
641     done = false;
642
643     configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
644     messageHandler = adoptNS([[SWMessageHandlerWithExpectedMessage alloc] init]);
645     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
646
647     handler = adoptNS([[SWSchemes alloc] init]);
648     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainForFirstLoadInterceptTestBytes });
649     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptInterceptingFirstLoadBytes });
650     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
651
652     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
653     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
654
655     expectedMessage = "Intercepted by worker";
656     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
657     [webView loadRequest:request];
658
659     TestWebKitAPI::Util::run(&done);
660     done = false;
661 }
662
663 TEST(ServiceWorkers, WaitForPolicyDelegate)
664 {
665     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
666
667     // Start with a clean slate data store
668     [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
669         done = true;
670     }];
671     TestWebKitAPI::Util::run(&done);
672     done = false;
673
674     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
675
676     RetainPtr<SWMessageHandlerWithExpectedMessage> messageHandler = adoptNS([[SWMessageHandlerWithExpectedMessage alloc] init]);
677     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
678
679     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
680     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainForFirstLoadInterceptTestBytes });
681     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptInterceptingFirstLoadBytes });
682     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
683
684     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
685     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
686
687     // Register a service worker and activate it.
688     expectedMessage = "Service Worker activated";
689     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
690     [webView loadRequest:request];
691
692     TestWebKitAPI::Util::run(&done);
693
694     webView = nullptr;
695     configuration = nullptr;
696     messageHandler = nullptr;
697     handler = nullptr;
698
699     done = false;
700
701     configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
702     messageHandler = adoptNS([[SWMessageHandlerWithExpectedMessage alloc] init]);
703     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
704
705     handler = adoptNS([[SWSchemes alloc] init]);
706     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainForFirstLoadInterceptTestBytes });
707     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptInterceptingFirstLoadBytes });
708     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
709
710     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
711     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
712
713     // Verify service worker is intercepting load.
714     expectedMessage = "Intercepted by worker";
715     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
716     [webView loadRequest:request];
717
718     TestWebKitAPI::Util::run(&done);
719     done = false;
720
721     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
722     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
723     auto delegate = adoptNS([[TestSWAsyncNavigationDelegate alloc] init]);
724     [webView setNavigationDelegate:delegate.get()];
725     [webView setUIDelegate:delegate.get()];
726
727     shouldAccept = true;
728     navigationFailed = false;
729     navigationComplete = false;
730
731     // Verify service worker load goes well when policy delegate is ok.
732     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
733     [webView loadRequest:request];
734     TestWebKitAPI::Util::run(&navigationComplete);
735
736     EXPECT_FALSE(navigationFailed);
737
738     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
739     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
740     [webView setNavigationDelegate:delegate.get()];
741     [webView setUIDelegate:delegate.get()];
742
743     shouldAccept = false;
744     navigationFailed = false;
745     navigationComplete = false;
746
747     // Verify service worker load fails well when policy delegate is not ok.
748     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
749     [webView loadRequest:request];
750     TestWebKitAPI::Util::run(&navigationComplete);
751
752     EXPECT_TRUE(navigationFailed);
753 }
754 #if WK_HAVE_C_SPI
755
756 void setConfigurationInjectedBundlePath(WKWebViewConfiguration* configuration)
757 {
758     WKRetainPtr<WKContextRef> context(AdoptWK, TestWebKitAPI::Util::createContextForInjectedBundleTest("InternalsInjectedBundleTest"));
759     configuration.processPool = (WKProcessPool *)context.get();
760     auto pool = configuration.processPool;
761     [pool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
762     [pool _setMaximumNumberOfProcesses:5];
763
764     configuration.websiteDataStore = (WKWebsiteDataStore *)WKContextGetWebsiteDataStore(context.get());
765 }
766
767 @interface RegularPageMessageHandler : NSObject <WKScriptMessageHandler>
768 @end
769
770 @implementation RegularPageMessageHandler
771 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
772 {
773     EXPECT_TRUE([[message body] isEqualToString:@"PASS"]);
774     done = true;
775 }
776 @end
777
778 static const char* regularPageWithoutConnectionBytes = R"SWRESOURCE(
779 <script>
780 var result = window.internals.hasServiceWorkerConnection() ? "FAIL" : "PASS";
781 window.webkit.messageHandlers.regularPage.postMessage(result);
782 </script>
783 )SWRESOURCE";
784
785 static const char* regularPageWithConnectionBytes = R"SWRESOURCE(
786 <script>
787 var result = window.internals.hasServiceWorkerConnection() ? "PASS" : "FAIL";
788 window.webkit.messageHandlers.regularPage.postMessage(result);
789 </script>
790 )SWRESOURCE";
791
792 TEST(ServiceWorkers, SWProcessConnectionCreation)
793 {
794     ASSERT(mainBytes);
795     ASSERT(scriptBytes);
796
797     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
798
799     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
800     setConfigurationInjectedBundlePath(configuration.get());
801
802     done = false;
803
804     [[configuration websiteDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
805         done = true;
806     }];
807     TestWebKitAPI::Util::run(&done);
808     done = false;
809
810     [[configuration websiteDataStore] fetchDataRecordsOfTypes:[NSSet setWithObject:WKWebsiteDataTypeServiceWorkerRegistrations] completionHandler:^(NSArray<WKWebsiteDataRecord *> *websiteDataRecords) {
811         EXPECT_EQ(0u, [websiteDataRecords count]);
812
813         done = true;
814     }];
815     TestWebKitAPI::Util::run(&done);
816     done = false;
817
818     RetainPtr<SWMessageHandler> messageHandler = adoptNS([[SWMessageHandler alloc] init]);
819     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
820     RetainPtr<RegularPageMessageHandler> regularPageMessageHandler = adoptNS([[RegularPageMessageHandler alloc] init]);
821     [[configuration userContentController] addScriptMessageHandler:regularPageMessageHandler.get() name:@"regularPage"];
822
823     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
824     handler->resources.set("sw://host/regularPageWithoutConnection.html", ResourceInfo { @"text/html", regularPageWithoutConnectionBytes });
825     handler->resources.set("sw://host/regularPageWithConnection.html", ResourceInfo { @"text/html", regularPageWithConnectionBytes });
826     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainBytes });
827     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptBytes });
828     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
829
830     RetainPtr<WKWebView> regularPageWebView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
831     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
832     RetainPtr<WKWebView> newRegularPageWebView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
833
834     // Test that a regular page does not trigger a service worker connection to storage process if there is no registered service worker.
835     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/regularPageWithoutConnection.html"]];
836
837     [regularPageWebView loadRequest:request];
838     TestWebKitAPI::Util::run(&done);
839     done = false;
840
841     // Test that a sw scheme page can register a service worker.
842     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
843
844     [webView loadRequest:request];
845     TestWebKitAPI::Util::run(&done);
846     done = false;
847     webView = nullptr;
848
849     // Now that a service worker is registered, the regular page should have a service worker connection.
850     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/regularPageWithConnection.html"]];
851
852     [regularPageWebView loadRequest:request];
853     TestWebKitAPI::Util::run(&done);
854     done = false;
855     regularPageWebView = nullptr;
856
857     [newRegularPageWebView loadRequest:request];
858     TestWebKitAPI::Util::run(&done);
859     done = false;
860     newRegularPageWebView = nullptr;
861
862     [[configuration websiteDataStore] fetchDataRecordsOfTypes:[NSSet setWithObject:WKWebsiteDataTypeServiceWorkerRegistrations] completionHandler:^(NSArray<WKWebsiteDataRecord *> *websiteDataRecords) {
863         EXPECT_EQ(1u, [websiteDataRecords count]);
864         EXPECT_TRUE([websiteDataRecords[0].displayName isEqualToString:@"sw host"]);
865
866         done = true;
867     }];
868
869     TestWebKitAPI::Util::run(&done);
870     done = false;
871
872     [[configuration websiteDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
873         done = true;
874     }];
875     TestWebKitAPI::Util::run(&done);
876 }
877
878 TEST(ServiceWorkers, StorageProcessConnectionCreation)
879 {
880     ASSERT(mainBytes);
881     ASSERT(scriptBytes);
882
883     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
884
885     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
886
887     done = false;
888     [[configuration websiteDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
889         done = true;
890     }];
891     TestWebKitAPI::Util::run(&done);
892     done = false;
893
894     configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
895     setConfigurationInjectedBundlePath(configuration.get());
896
897     RetainPtr<RegularPageMessageHandler> regularPageMessageHandler = adoptNS([[RegularPageMessageHandler alloc] init]);
898     [[configuration userContentController] addScriptMessageHandler:regularPageMessageHandler.get() name:@"regularPage"];
899
900     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
901     handler->resources.set("sw://host/regularPageWithoutConnection.html", ResourceInfo { @"text/html", regularPageWithoutConnectionBytes });
902     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
903
904     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
905
906     EXPECT_EQ(0, webView.get().configuration.processPool._storageProcessIdentifier);
907
908     // Test that a regular page does not trigger a storage process if there is no registered service worker.
909     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/regularPageWithoutConnection.html"]];
910
911     [webView loadRequest:request];
912     TestWebKitAPI::Util::run(&done);
913     done = false;
914
915     // Make sure than loading the simple page did not start a storage process.
916     EXPECT_EQ(0, webView.get().configuration.processPool._storageProcessIdentifier);
917 }
918
919 static const char* mainBytesWithScope = R"SWRESOURCE(
920 <script>
921
922 function log(msg)
923 {
924     window.webkit.messageHandlers.sw.postMessage(msg);
925 }
926
927 navigator.serviceWorker.addEventListener("message", function(event) {
928     log("Message from worker: " + event.data);
929 });
930
931 try {
932
933 navigator.serviceWorker.register('/sw.js', {scope: 'whateverscope'}).then(function(reg) {
934     reg.installing.postMessage("Hello from the web page");
935 }).catch(function(error) {
936     log("Registration failed with: " + error);
937 });
938 } catch(e) {
939     log("Exception: " + e);
940 }
941
942 </script>
943 )SWRESOURCE";
944
945 TEST(ServiceWorkers, ServiceWorkerProcessCreation)
946 {
947     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
948
949     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
950     setConfigurationInjectedBundlePath(configuration.get());
951
952     done = false;
953
954     [[configuration websiteDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
955         done = true;
956     }];
957     TestWebKitAPI::Util::run(&done);
958     done = false;
959
960     [[configuration websiteDataStore] fetchDataRecordsOfTypes:[NSSet setWithObject:WKWebsiteDataTypeServiceWorkerRegistrations] completionHandler:^(NSArray<WKWebsiteDataRecord *> *websiteDataRecords) {
961
962         done = true;
963     }];
964     TestWebKitAPI::Util::run(&done);
965     done = false;
966
967     RetainPtr<SWMessageHandler> messageHandler = adoptNS([[SWMessageHandler alloc] init]);
968     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
969     RetainPtr<RegularPageMessageHandler> regularPageMessageHandler = adoptNS([[RegularPageMessageHandler alloc] init]);
970     [[configuration userContentController] addScriptMessageHandler:regularPageMessageHandler.get() name:@"regularPage"];
971
972     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
973     handler->resources.set("sw://host/regularPageWithoutConnection.html", ResourceInfo { @"text/html", regularPageWithoutConnectionBytes });
974     handler->resources.set("sw://host/regularPageWithConnection.html", ResourceInfo { @"text/html", regularPageWithConnectionBytes });
975     handler->resources.set("sw://host/mainWithScope.html", ResourceInfo { @"text/html", mainBytesWithScope });
976     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainBytes });
977     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptBytes });
978     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
979
980     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
981
982     // Load a page that registers a service worker.
983     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/mainWithScope.html"]];
984
985     [webView loadRequest:request];
986     TestWebKitAPI::Util::run(&done);
987     done = false;
988     webView = nullptr;
989
990     // Now that a sw is registered, let's create a new configuration and try loading a regular page, there should be no service worker process created.
991     RetainPtr<WKWebViewConfiguration> newConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
992     setConfigurationInjectedBundlePath(newConfiguration.get());
993     newConfiguration.get().websiteDataStore = [configuration websiteDataStore];
994
995     [[newConfiguration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
996     [[newConfiguration userContentController] addScriptMessageHandler:regularPageMessageHandler.get() name:@"regularPage"];
997     [newConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
998
999     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:newConfiguration.get()]);
1000     EXPECT_EQ(1u, webView.get().configuration.processPool._webProcessCount);
1001     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/regularPageWithConnection.html"]];
1002     [webView loadRequest:request];
1003     TestWebKitAPI::Util::run(&done);
1004     done = false;
1005
1006     // Make sure that loading the simple page did not start the service worker process.
1007     EXPECT_EQ(1u, webView.get().configuration.processPool._webProcessCount);
1008
1009     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:newConfiguration.get()]);
1010     EXPECT_EQ(2u, webView.get().configuration.processPool._webProcessCount);
1011     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
1012     [webView loadRequest:request];
1013     TestWebKitAPI::Util::run(&done);
1014     done = false;
1015
1016     // Make sure that loading this page did start the service worker process.
1017     EXPECT_EQ(3u, webView.get().configuration.processPool._webProcessCount);
1018
1019     [[configuration websiteDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
1020         done = true;
1021     }];
1022     TestWebKitAPI::Util::run(&done);
1023     done = false;
1024 }
1025
1026 TEST(ServiceWorkers, HasServiceWorkerRegistrationBit)
1027 {
1028     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
1029
1030     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
1031     setConfigurationInjectedBundlePath(configuration.get());
1032
1033     done = false;
1034
1035     [[configuration websiteDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
1036         done = true;
1037     }];
1038     TestWebKitAPI::Util::run(&done);
1039     done = false;
1040
1041     [[configuration websiteDataStore] fetchDataRecordsOfTypes:[NSSet setWithObject:WKWebsiteDataTypeServiceWorkerRegistrations] completionHandler:^(NSArray<WKWebsiteDataRecord *> *websiteDataRecords) {
1042
1043         done = true;
1044     }];
1045     TestWebKitAPI::Util::run(&done);
1046     done = false;
1047
1048     RetainPtr<SWMessageHandler> messageHandler = adoptNS([[SWMessageHandler alloc] init]);
1049     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
1050     RetainPtr<RegularPageMessageHandler> regularPageMessageHandler = adoptNS([[RegularPageMessageHandler alloc] init]);
1051     [[configuration userContentController] addScriptMessageHandler:regularPageMessageHandler.get() name:@"regularPage"];
1052
1053     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
1054     handler->resources.set("sw://host/regularPageWithoutConnection.html", ResourceInfo { @"text/html", regularPageWithoutConnectionBytes });
1055     handler->resources.set("sw://host/regularPageWithConnection.html", ResourceInfo { @"text/html", regularPageWithConnectionBytes });
1056     handler->resources.set("sw://host/mainWithScope.html", ResourceInfo { @"text/html", mainBytesWithScope });
1057     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainBytes });
1058     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptBytes });
1059     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
1060
1061     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
1062
1063     // Load a page that registers a service worker.
1064     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/mainWithScope.html"]];
1065
1066     [webView loadRequest:request];
1067     TestWebKitAPI::Util::run(&done);
1068     done = false;
1069     webView = nullptr;
1070
1071     // Now that a sw is registered, let's create a new configuration and try loading a regular page
1072     RetainPtr<WKWebViewConfiguration> newConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
1073     setConfigurationInjectedBundlePath(newConfiguration.get());
1074
1075     [[newConfiguration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
1076     [[newConfiguration userContentController] addScriptMessageHandler:regularPageMessageHandler.get() name:@"regularPage"];
1077     [newConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
1078
1079     newConfiguration.get().websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
1080     [newConfiguration.get().websiteDataStore _setServiceWorkerRegistrationDirectory: @"~/nonexistingfolder"];
1081
1082     [newConfiguration.get().processPool _setMaximumNumberOfProcesses:1];
1083
1084     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:newConfiguration.get()]);
1085     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/regularPageWithoutConnection.html"]];
1086     [webView loadRequest:request];
1087     TestWebKitAPI::Util::run(&done);
1088     done = false;
1089
1090     // There should be no storage process created.
1091     EXPECT_EQ(0, webView.get().configuration.processPool._storageProcessIdentifier);
1092
1093     // Let's use the web site data store that has service worker and load a page.
1094     newConfiguration.get().websiteDataStore = [configuration websiteDataStore];
1095
1096     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:newConfiguration.get()]);
1097     EXPECT_EQ(1u, webView.get().configuration.processPool._webProcessCount);
1098     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/regularPageWithConnection.html"]];
1099     [webView loadRequest:request];
1100     TestWebKitAPI::Util::run(&done);
1101     done = false;
1102
1103     // Make sure that storage process is launched.
1104     EXPECT_NE(0, webView.get().configuration.processPool._storageProcessIdentifier);
1105
1106     // Make sure that loading the simple page did not start the service worker process.
1107     EXPECT_EQ(1u, webView.get().configuration.processPool._webProcessCount);
1108
1109     [[configuration websiteDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
1110         done = true;
1111     }];
1112     TestWebKitAPI::Util::run(&done);
1113     done = false;
1114 }
1115
1116 static const char* regularPageGrabbingCacheStorageDirectory = R"SWRESOURCE(
1117 <script>
1118 async function getResult()
1119 {
1120     var result = await window.internals.cacheStorageEngineRepresentation();
1121     window.webkit.messageHandlers.sw.postMessage(result);
1122 }
1123 getResult();
1124 </script>
1125 )SWRESOURCE";
1126
1127 @interface DirectoryPageMessageHandler : NSObject <WKScriptMessageHandler>
1128 @end
1129
1130 @implementation DirectoryPageMessageHandler
1131 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
1132 {
1133     retrievedString = [message body];
1134     done = true;
1135 }
1136 @end
1137
1138 TEST(ServiceWorkers, ServiceWorkerAndCacheStorageDefaultDirectories)
1139 {
1140     ASSERT(mainBytes);
1141     ASSERT(scriptBytes);
1142
1143     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
1144
1145     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
1146
1147     configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
1148     setConfigurationInjectedBundlePath(configuration.get());
1149
1150     RetainPtr<DirectoryPageMessageHandler> directoryPageMessageHandler = adoptNS([[DirectoryPageMessageHandler alloc] init]);
1151     [[configuration userContentController] addScriptMessageHandler:directoryPageMessageHandler.get() name:@"sw"];
1152
1153     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
1154     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainBytes });
1155     handler->resources.set("sw://host/regularPageGrabbingCacheStorageDirectory.html", ResourceInfo { @"text/html", regularPageGrabbingCacheStorageDirectory });
1156     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptBytes });
1157     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
1158
1159     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
1160     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
1161
1162     [webView loadRequest:request];
1163     TestWebKitAPI::Util::run(&done);
1164     done = false;
1165     EXPECT_TRUE([[configuration websiteDataStore] _hasRegisteredServiceWorker]);
1166
1167     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
1168     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
1169
1170     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/regularPageGrabbingCacheStorageDirectory.html"]];
1171     [webView loadRequest:request];
1172     TestWebKitAPI::Util::run(&done);
1173     done = false;
1174     EXPECT_TRUE(retrievedString.contains("/Caches/TestWebKitAPI/WebKit/CacheStorage"));
1175
1176     [[configuration websiteDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
1177         done = true;
1178     }];
1179     TestWebKitAPI::Util::run(&done);
1180     done = false;
1181 }
1182
1183 TEST(ServiceWorkers, ServiceWorkerAndCacheStorageSpecificDirectories)
1184 {
1185     ASSERT(mainBytes);
1186     ASSERT(scriptBytes);
1187
1188     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
1189
1190     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
1191
1192     configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
1193     setConfigurationInjectedBundlePath(configuration.get());
1194     auto websiteDataStore = [configuration websiteDataStore];
1195     [websiteDataStore _setCacheStorageDirectory:@"/var/tmp"];
1196     [websiteDataStore _setServiceWorkerRegistrationDirectory:@"/var/tmp"];
1197
1198     RetainPtr<DirectoryPageMessageHandler> directoryPageMessageHandler = adoptNS([[DirectoryPageMessageHandler alloc] init]);
1199     [[configuration userContentController] addScriptMessageHandler:directoryPageMessageHandler.get() name:@"sw"];
1200
1201     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
1202     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainBytes });
1203     handler->resources.set("sw://host/regularPageGrabbingCacheStorageDirectory.html", ResourceInfo { @"text/html", regularPageGrabbingCacheStorageDirectory });
1204     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptBytes });
1205     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
1206
1207     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
1208     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
1209
1210     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
1211     [webView loadRequest:request];
1212     TestWebKitAPI::Util::run(&done);
1213     done = false;
1214     EXPECT_TRUE([websiteDataStore _hasRegisteredServiceWorker]);
1215
1216     webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
1217     request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/regularPageGrabbingCacheStorageDirectory.html"]];
1218
1219     [webView loadRequest:request];
1220     TestWebKitAPI::Util::run(&done);
1221     done = false;
1222     EXPECT_TRUE(retrievedString.contains("\"path\": \"/var/tmp\""));
1223
1224     [[configuration websiteDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
1225         done = true;
1226     }];
1227     TestWebKitAPI::Util::run(&done);
1228     done = false;
1229 }
1230
1231 #endif // WK_HAVE_C_SPI
1232
1233 TEST(ServiceWorkers, NonDefaultSessionID)
1234 {
1235     [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
1236
1237     NSURL *serviceWorkersPath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/CustomWebsiteData/ServiceWorkers/" stringByExpandingTildeInPath] isDirectory:YES];
1238     [[NSFileManager defaultManager] removeItemAtURL:serviceWorkersPath error:nil];
1239     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:serviceWorkersPath.path]);
1240     NSURL *idbPath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/CustomWebsiteData/IndexedDB/" stringByExpandingTildeInPath] isDirectory:YES];
1241     [[NSFileManager defaultManager] removeItemAtURL:idbPath error:nil];
1242     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:idbPath.path]);
1243
1244     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
1245     RetainPtr<_WKWebsiteDataStoreConfiguration> websiteDataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
1246     websiteDataStoreConfiguration.get()._serviceWorkerRegistrationDirectory = serviceWorkersPath;
1247     websiteDataStoreConfiguration.get()._indexedDBDatabaseDirectory = idbPath;
1248
1249     configuration.get().websiteDataStore = [[[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()] autorelease];
1250
1251     RetainPtr<SWMessageHandlerWithExpectedMessage> messageHandler = adoptNS([[SWMessageHandlerWithExpectedMessage alloc] init]);
1252     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
1253
1254     RetainPtr<SWSchemes> handler = adoptNS([[SWSchemes alloc] init]);
1255     handler->resources.set("sw://host/main.html", ResourceInfo { @"text/html", mainBytesForSessionIDTest });
1256     handler->resources.set("sw://host/sw.js", ResourceInfo { @"application/javascript", scriptBytesForSessionIDTest });
1257     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"SW"];
1258
1259     expectedMessage = "PASS: activation successful";
1260     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
1261     [webView.get().configuration.processPool _registerURLSchemeServiceWorkersCanHandle:@"sw"];
1262
1263     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"sw://host/main.html"]];
1264     [webView loadRequest:request];
1265
1266     TestWebKitAPI::Util::run(&done);
1267     done = false;
1268
1269     webView = nullptr;
1270
1271     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:serviceWorkersPath.path]);
1272
1273     [configuration.get().websiteDataStore fetchDataRecordsOfTypes:[NSSet setWithObject:WKWebsiteDataTypeServiceWorkerRegistrations] completionHandler:^(NSArray<WKWebsiteDataRecord *> *websiteDataRecords) {
1274         EXPECT_EQ(1u, [websiteDataRecords count]);
1275         EXPECT_TRUE([websiteDataRecords[0].displayName isEqualToString:@"sw host"]);
1276
1277         done = true;
1278     }];
1279
1280     TestWebKitAPI::Util::run(&done);
1281     done = false;
1282 }
1283
1284 #endif // WK_API_ENABLED