Add SPI for setting media cache and key location on _WKWebsiteDataStoreConfiguration
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / WebsiteDataStoreCustomPaths.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 "TCPServer.h"
30 #import "Test.h"
31 #import "TestNavigationDelegate.h"
32 #import "TestWKWebView.h"
33 #import <WebKit/WKPreferencesRef.h>
34 #import <WebKit/WKProcessPoolPrivate.h>
35 #import <WebKit/WKUserContentControllerPrivate.h>
36 #import <WebKit/WKWebViewConfigurationPrivate.h>
37 #import <WebKit/WKWebViewPrivate.h>
38 #import <WebKit/WKWebsiteDataStorePrivate.h>
39 #import <WebKit/WebKit.h>
40 #import <WebKit/_WKProcessPoolConfiguration.h>
41 #import <WebKit/_WKUserStyleSheet.h>
42 #import <WebKit/_WKWebsiteDataStoreConfiguration.h>
43 #import <wtf/Deque.h>
44 #import <wtf/RetainPtr.h>
45 #import <wtf/text/WTFString.h>
46
47 static bool receivedScriptMessage;
48 static Deque<RetainPtr<WKScriptMessage>> scriptMessages;
49
50 @interface WebsiteDataStoreCustomPathsMessageHandler : NSObject <WKScriptMessageHandler, WKNavigationDelegate>
51 @end
52
53 @implementation WebsiteDataStoreCustomPathsMessageHandler
54
55 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
56 {
57     receivedScriptMessage = true;
58     scriptMessages.append(message);
59 }
60
61 - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
62 {
63     // Overwrite the default policy which launches a new web process and reload page on crash.
64 }
65
66 @end
67
68 static WKScriptMessage *getNextMessage()
69 {
70     if (scriptMessages.isEmpty()) {
71         receivedScriptMessage = false;
72         TestWebKitAPI::Util::run(&receivedScriptMessage);
73     }
74
75     return [[scriptMessages.takeFirst() retain] autorelease];
76 }
77
78 enum class ShouldEnableProcessPrewarming { No, Yes };
79
80 static void runWebsiteDataStoreCustomPaths(ShouldEnableProcessPrewarming shouldEnableProcessPrewarming)
81 {
82     auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
83     processPoolConfiguration.get().prewarmsProcessesAutomatically = shouldEnableProcessPrewarming == ShouldEnableProcessPrewarming::Yes ? YES : NO;
84     auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
85
86     RetainPtr<WebsiteDataStoreCustomPathsMessageHandler> handler = adoptNS([[WebsiteDataStoreCustomPathsMessageHandler alloc] init]);
87     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
88     [[configuration userContentController] addScriptMessageHandler:handler.get() name:@"testHandler"];
89
90     NSURL *sqlPath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/CustomWebsiteData/WebSQL/" stringByExpandingTildeInPath] isDirectory:YES];
91     NSURL *idbPath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/CustomWebsiteData/IndexedDB/" stringByExpandingTildeInPath] isDirectory:YES];
92     NSURL *localStoragePath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/CustomWebsiteData/LocalStorage/" stringByExpandingTildeInPath] isDirectory:YES];
93     NSURL *cookieStorageFile = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/CustomWebsiteData/CookieStorage/Cookie.File" stringByExpandingTildeInPath] isDirectory:NO];
94     NSURL *resourceLoadStatisticsPath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/CustomWebsiteData/ResourceLoadStatistics/" stringByExpandingTildeInPath] isDirectory:YES];
95
96     NSURL *defaultSQLPath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/WebsiteData/WebSQL/" stringByExpandingTildeInPath] isDirectory:YES];
97     NSURL *defaultIDBPath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/WebsiteData/IndexedDB/" stringByExpandingTildeInPath] isDirectory:YES];
98     NSURL *defaultLocalStoragePath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/WebsiteData/LocalStorage/" stringByExpandingTildeInPath] isDirectory:YES];
99     NSURL *defaultResourceLoadStatisticsPath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/WebsiteData/ResourceLoadStatistics/" stringByExpandingTildeInPath] isDirectory:YES];
100
101     [[NSFileManager defaultManager] removeItemAtURL:sqlPath error:nil];
102     [[NSFileManager defaultManager] removeItemAtURL:idbPath error:nil];
103     [[NSFileManager defaultManager] removeItemAtURL:localStoragePath error:nil];
104     [[NSFileManager defaultManager] removeItemAtURL:cookieStorageFile error:nil];
105     [[NSFileManager defaultManager] removeItemAtURL:resourceLoadStatisticsPath error:nil];
106     [[NSFileManager defaultManager] removeItemAtURL:defaultSQLPath error:nil];
107     [[NSFileManager defaultManager] removeItemAtURL:defaultIDBPath error:nil];
108     [[NSFileManager defaultManager] removeItemAtURL:defaultLocalStoragePath error:nil];
109     [[NSFileManager defaultManager] removeItemAtURL:defaultResourceLoadStatisticsPath error:nil];
110
111     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:sqlPath.path]);
112     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:idbPath.path]);
113     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:localStoragePath.path]);
114     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:cookieStorageFile.path]);
115     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:resourceLoadStatisticsPath.path]);
116     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:defaultSQLPath.path]);
117     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:defaultIDBPath.path]);
118     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:defaultLocalStoragePath.path]);
119     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsPath.path]);
120
121     RetainPtr<_WKWebsiteDataStoreConfiguration> websiteDataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
122     websiteDataStoreConfiguration.get()._webSQLDatabaseDirectory = sqlPath;
123     websiteDataStoreConfiguration.get()._indexedDBDatabaseDirectory = idbPath;
124     websiteDataStoreConfiguration.get()._webStorageDirectory = localStoragePath;
125     websiteDataStoreConfiguration.get()._cookieStorageFile = cookieStorageFile;
126     websiteDataStoreConfiguration.get()._resourceLoadStatisticsDirectory = resourceLoadStatisticsPath;
127
128     configuration.get().websiteDataStore = [[[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()] autorelease];
129     configuration.get().processPool = processPool.get();
130
131     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
132     [webView setNavigationDelegate:handler.get()];
133
134     auto preferences = (__bridge WKPreferencesRef)[[webView configuration] preferences];
135     WKPreferencesSetWebSQLDisabled(preferences, false);
136
137     NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"WebsiteDataStoreCustomPaths" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
138     [webView loadRequest:request];
139
140     EXPECT_FALSE([WKWebsiteDataStore _defaultDataStoreExists]);
141
142     // We expect 4 messages, 1 each for WebSQL, IndexedDB, cookies, and localStorage.
143     EXPECT_STREQ([getNextMessage().body UTF8String], "localstorage written");
144     EXPECT_STREQ([getNextMessage().body UTF8String], "cookie written");
145     EXPECT_STREQ([getNextMessage().body UTF8String], "Exception: QuotaExceededError: The quota has been exceeded.");
146     EXPECT_STREQ([getNextMessage().body UTF8String], "Success opening indexed database");
147
148     [[[webView configuration] processPool] _syncNetworkProcessCookies];
149
150     // Forcibly shut down everything of WebKit that we can.
151     auto pid = [webView _webProcessIdentifier];
152     if (pid)
153         kill(pid, SIGKILL);
154
155     webView = nil;
156     handler = nil;
157     configuration = nil;
158
159     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:sqlPath.path]);
160     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:localStoragePath.path]);
161
162 #if PLATFORM(IOS_FAMILY) || (__MAC_OS_X_VERSION_MIN_REQUIRED < 101300)
163     int retryCount = 30;
164     while (retryCount--) {
165         if ([[NSFileManager defaultManager] fileExistsAtPath:cookieStorageFile.path])
166             break;
167         TestWebKitAPI::Util::sleep(0.1);
168     }
169     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:cookieStorageFile.path]);
170
171     // Note: The format of the cookie file on disk is proprietary and opaque, so this is fragile to future changes.
172     // But right now, it is reliable to scan the file for the ASCII string of the cookie name we set.
173     // This helps verify that the cookie file was actually written to as we'd expect.
174     auto data = adoptNS([[NSData alloc] initWithContentsOfURL:cookieStorageFile]);
175     char bytes[] = "testkey";
176     auto cookieKeyData = adoptNS([[NSData alloc] initWithBytes:(void *)bytes length:strlen(bytes)]);
177     auto result = [data rangeOfData:cookieKeyData.get() options:0 range:NSMakeRange(0, data.get().length)];
178     EXPECT_NE((const long)result.location, NSNotFound);
179 #endif
180
181 #if PLATFORM(MAC)
182     // FIXME: The default SQL and LocalStorage paths are being used for something, but they shouldn't be. (theses should be false, not true)
183     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:defaultSQLPath.path]);
184     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:defaultLocalStoragePath.path]);
185 #endif
186
187     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:idbPath.path]);
188     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:defaultIDBPath.path]);
189     RetainPtr<NSURL> fileIDBPath = [[idbPath URLByAppendingPathComponent:@"v1"] URLByAppendingPathComponent:@"file__0"];
190     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:fileIDBPath.get().path]);
191
192     RetainPtr<WKWebsiteDataStore> dataStore = [[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()];
193     RetainPtr<NSSet> types = adoptNS([[NSSet alloc] initWithObjects:WKWebsiteDataTypeIndexedDBDatabases, nil]);
194
195     // Subframe of different origins may also create IndexedDB files.
196     RetainPtr<NSURL> url1 = [[NSBundle mainBundle] URLForResource:@"IndexedDB" withExtension:@"sqlite3" subdirectory:@"TestWebKitAPI.resources"];
197     RetainPtr<NSURL> url2 = [[NSBundle mainBundle] URLForResource:@"IndexedDB" withExtension:@"sqlite3-shm" subdirectory:@"TestWebKitAPI.resources"];
198     RetainPtr<NSURL> url3 = [[NSBundle mainBundle] URLForResource:@"IndexedDB" withExtension:@"sqlite3-wal" subdirectory:@"TestWebKitAPI.resources"];
199
200     RetainPtr<NSURL> frameIDBPath = [[fileIDBPath URLByAppendingPathComponent:@"https_apple.com_0"] URLByAppendingPathComponent:@"WebsiteDataStoreCustomPaths"];
201     [[NSFileManager defaultManager] createDirectoryAtURL:frameIDBPath.get() withIntermediateDirectories:YES attributes:nil error:nil];
202
203     [[NSFileManager defaultManager] copyItemAtURL:url1.get() toURL:[frameIDBPath.get() URLByAppendingPathComponent:@"IndexedDB.sqlite3"] error:nil];
204     [[NSFileManager defaultManager] copyItemAtURL:url2.get() toURL:[frameIDBPath.get() URLByAppendingPathComponent:@"IndexedDB.sqlite3-shm"] error:nil];
205     [[NSFileManager defaultManager] copyItemAtURL:url3.get() toURL:[frameIDBPath.get() URLByAppendingPathComponent:@"IndexedDB.sqlite3-wal"] error:nil];
206
207     RetainPtr<NSURL> frameIDBPath2 = [[fileIDBPath URLByAppendingPathComponent:@"https_webkit.org_0"] URLByAppendingPathComponent:@"WebsiteDataStoreCustomPaths"];
208     [[NSFileManager defaultManager] createDirectoryAtURL:frameIDBPath2.get() withIntermediateDirectories:YES attributes:nil error:nil];
209
210     [[NSFileManager defaultManager] copyItemAtURL:url1.get() toURL:[frameIDBPath2.get() URLByAppendingPathComponent:@"IndexedDB.sqlite3"] error:nil];
211     [[NSFileManager defaultManager] copyItemAtURL:url2.get() toURL:[frameIDBPath2.get() URLByAppendingPathComponent:@"IndexedDB.sqlite3-shm"] error:nil];
212     [[NSFileManager defaultManager] copyItemAtURL:url3.get() toURL:[frameIDBPath2.get() URLByAppendingPathComponent:@"IndexedDB.sqlite3-wal"] error:nil];
213
214     [dataStore fetchDataRecordsOfTypes:types.get() completionHandler:^(NSArray<WKWebsiteDataRecord *> * records) {
215         EXPECT_EQ([records count], (unsigned long)3);
216         for (id record in records) {
217             if ([[record displayName] isEqual: @"apple.com"]) {
218                 [dataStore removeDataOfTypes:types.get() forDataRecords:[NSArray arrayWithObject:record] completionHandler:^() {
219                     receivedScriptMessage = true;
220                     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:frameIDBPath.get().path]);
221                 }];
222             }
223         }
224     }];
225     receivedScriptMessage = false;
226     TestWebKitAPI::Util::run(&receivedScriptMessage);
227
228     [dataStore removeDataOfTypes:types.get() modifiedSince:[NSDate distantPast] completionHandler:[]() {
229         receivedScriptMessage = true;
230     }];
231     receivedScriptMessage = false;
232     TestWebKitAPI::Util::run(&receivedScriptMessage);
233
234     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:fileIDBPath.get().path]);
235
236     // Now, with brand new WKWebsiteDataStores pointing at the same custom cookie storage location,
237     // in newly fired up NetworkProcesses, verify that the fetch and delete APIs work as expected.
238
239     [processPool _terminateNetworkProcess];
240     auto newCustomDataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()]);
241
242     [newCustomDataStore fetchDataRecordsOfTypes:[NSSet setWithObjects:WKWebsiteDataTypeCookies, nil] completionHandler:^(NSArray<WKWebsiteDataRecord *> * records) {
243         EXPECT_GT([records count], (unsigned long)0);
244         receivedScriptMessage = true;
245     }];
246
247     receivedScriptMessage = false;
248     TestWebKitAPI::Util::run(&receivedScriptMessage);
249
250     [processPool _terminateNetworkProcess];
251     newCustomDataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()]);
252
253     [newCustomDataStore removeDataOfTypes:[NSSet setWithObjects:WKWebsiteDataTypeCookies, nil] modifiedSince:[NSDate distantPast] completionHandler:^ {
254         receivedScriptMessage = true;
255     }];
256
257     receivedScriptMessage = false;
258     TestWebKitAPI::Util::run(&receivedScriptMessage);
259
260     // This time, reuse the same network process but still do a new websitedatastore, to make sure even an existing network process
261     // gets the new datastore.
262     newCustomDataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()]);
263
264     [newCustomDataStore fetchDataRecordsOfTypes:[NSSet setWithObjects:WKWebsiteDataTypeCookies, nil] completionHandler:^(NSArray<WKWebsiteDataRecord *> * records) {
265         EXPECT_EQ([records count], (unsigned long)0);
266         receivedScriptMessage = true;
267     }];
268
269     receivedScriptMessage = false;
270     TestWebKitAPI::Util::run(&receivedScriptMessage);
271
272     EXPECT_FALSE([WKWebsiteDataStore _defaultDataStoreExists]);
273 }
274
275 TEST(WebKit, WebsiteDataStoreCustomPathsWithoutPrewarming)
276 {
277     runWebsiteDataStoreCustomPaths(ShouldEnableProcessPrewarming::No);
278 }
279
280 TEST(WebKit, WebsiteDataStoreCustomPathsWithPrewarming)
281 {
282     runWebsiteDataStoreCustomPaths(ShouldEnableProcessPrewarming::Yes);
283 }
284
285 TEST(WebKit, CustomDataStorePathsVersusCompletionHandlers)
286 {
287     // Copy the baked database files to the database directory
288     NSURL *url1 = [[NSBundle mainBundle] URLForResource:@"SimpleServiceWorkerRegistrations-4" withExtension:@"sqlite3" subdirectory:@"TestWebKitAPI.resources"];
289
290     NSURL *swPath = [NSURL fileURLWithPath:[@"~/Library/Caches/TestWebKitAPI/WebKit/ServiceWorkers/" stringByExpandingTildeInPath]];
291     [[NSFileManager defaultManager] removeItemAtURL:swPath error:nil];
292     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:swPath.path]);
293
294     [[NSFileManager defaultManager] createDirectoryAtURL:swPath withIntermediateDirectories:YES attributes:nil error:nil];
295     [[NSFileManager defaultManager] copyItemAtURL:url1 toURL:[swPath URLByAppendingPathComponent:@"ServiceWorkerRegistrations-4.sqlite3"] error:nil];
296
297     auto websiteDataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
298     websiteDataStoreConfiguration.get()._serviceWorkerRegistrationDirectory = swPath;
299     auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()]);
300
301     // Fetch SW records
302     auto websiteDataTypes = adoptNS([[NSSet alloc] initWithArray:@[WKWebsiteDataTypeServiceWorkerRegistrations]]);
303     static bool readyToContinue;
304     [dataStore fetchDataRecordsOfTypes:websiteDataTypes.get() completionHandler:^(NSArray<WKWebsiteDataRecord *> *dataRecords) {
305         EXPECT_EQ(1U, dataRecords.count);
306         readyToContinue = true;
307     }];
308     TestWebKitAPI::Util::run(&readyToContinue);
309     readyToContinue = false;
310
311     // Fetch records again, this time releasing our reference to the data store while the request is in flight.
312     [dataStore fetchDataRecordsOfTypes:websiteDataTypes.get() completionHandler:^(NSArray<WKWebsiteDataRecord *> *dataRecords) {
313         EXPECT_EQ(1U, dataRecords.count);
314         readyToContinue = true;
315     }];
316     dataStore = nil;
317     TestWebKitAPI::Util::run(&readyToContinue);
318     readyToContinue = false;
319
320     // Delete all SW records, releasing our reference to the data store while the request is in flight.
321     dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()]);
322     [dataStore removeDataOfTypes:websiteDataTypes.get() modifiedSince:[NSDate distantPast] completionHandler:^() {
323         readyToContinue = true;
324     }];
325     dataStore = nil;
326     TestWebKitAPI::Util::run(&readyToContinue);
327     readyToContinue = false;
328
329     // The records should have been deleted, and the callback should have been made.
330     // Now refetch the records to verify they are gone.
331     dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()]);
332     [dataStore fetchDataRecordsOfTypes:websiteDataTypes.get() completionHandler:^(NSArray<WKWebsiteDataRecord *> *dataRecords) {
333         EXPECT_EQ(0U, dataRecords.count);
334         readyToContinue = true;
335     }];
336     TestWebKitAPI::Util::run(&readyToContinue);
337 }
338
339 TEST(WebKit, CustomDataStoreDestroyWhileFetchingNetworkProcessData)
340 {
341     NSURL *cookieStorageFile = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/CustomWebsiteData/CookieStorage/Cookie.File" stringByExpandingTildeInPath] isDirectory:NO];
342     [[NSFileManager defaultManager] removeItemAtURL:cookieStorageFile error:nil];
343
344     auto websiteDataTypes = adoptNS([[NSSet alloc] initWithArray:@[WKWebsiteDataTypeCookies]]);
345     static bool readyToContinue;
346
347     auto websiteDataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
348     websiteDataStoreConfiguration.get()._cookieStorageFile = cookieStorageFile;
349
350     @autoreleasepool {
351         auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()]);
352
353         // Fetch records
354         [dataStore fetchDataRecordsOfTypes:websiteDataTypes.get() completionHandler:^(NSArray<WKWebsiteDataRecord *> *dataRecords) {
355             EXPECT_EQ((int)dataRecords.count, 0);
356             readyToContinue = true;
357         }];
358         TestWebKitAPI::Util::run(&readyToContinue);
359         readyToContinue = false;
360
361         // Fetch records again, this time releasing our reference to the data store while the request is in flight.
362         [dataStore fetchDataRecordsOfTypes:websiteDataTypes.get() completionHandler:^(NSArray<WKWebsiteDataRecord *> *dataRecords) {
363             EXPECT_EQ((int)dataRecords.count, 0);
364             readyToContinue = true;
365         }];
366         dataStore = nil;
367     }
368     TestWebKitAPI::Util::run(&readyToContinue);
369     readyToContinue = false;
370
371     @autoreleasepool {
372         auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()]);
373         [dataStore fetchDataRecordsOfTypes:websiteDataTypes.get() completionHandler:^(NSArray<WKWebsiteDataRecord *> *dataRecords) {
374             EXPECT_EQ((int)dataRecords.count, 0);
375             readyToContinue = true;
376         }];
377
378         // Terminate the network process while a query is pending.
379         auto* allProcessPools = [WKProcessPool _allProcessPoolsForTesting];
380         ASSERT_EQ(1U, [allProcessPools count]);
381         auto* processPool = allProcessPools[0];
382         while (![processPool _networkProcessIdentifier])
383             TestWebKitAPI::Util::sleep(0.01);
384         kill([processPool _networkProcessIdentifier], SIGKILL);
385         allProcessPools = nil;
386         dataStore = nil;
387     }
388
389     TestWebKitAPI::Util::run(&readyToContinue);
390     readyToContinue = false;
391 }
392
393 TEST(WebKit, WebsiteDataStoreEphemeral)
394 {
395     RetainPtr<WebsiteDataStoreCustomPathsMessageHandler> handler = adoptNS([[WebsiteDataStoreCustomPathsMessageHandler alloc] init]);
396     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
397     [[configuration userContentController] addScriptMessageHandler:handler.get() name:@"testHandler"];
398
399     NSURL *defaultResourceLoadStatisticsPath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/WebsiteData/ResourceLoadStatistics/" stringByExpandingTildeInPath] isDirectory:YES];
400
401     [[NSFileManager defaultManager] removeItemAtURL:defaultResourceLoadStatisticsPath error:nil];
402
403     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsPath.path]);
404
405     configuration.get().websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
406     [configuration.get().websiteDataStore _setResourceLoadStatisticsEnabled:YES];
407
408     // We expect the directory to be created by starting up the data store machinery, but not the data file.
409     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsPath.path]);
410
411     NSURL *defaultResourceLoadStatisticsFilePath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/WebsiteData/ResourceLoadStatistics/full_browsing_session_resourceLog.plist" stringByExpandingTildeInPath] isDirectory:NO];
412     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsFilePath.path]);
413
414     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
415
416     NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"WebsiteDataStoreCustomPaths" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
417     [webView loadRequest:request];
418
419     [[[webView configuration] processPool] _syncNetworkProcessCookies];
420
421     // Forcibly shut down everything of WebKit that we can.
422     auto pid = [webView _webProcessIdentifier];
423     if (pid)
424         kill(pid, SIGKILL);
425
426     webView = nil;
427     handler = nil;
428     configuration = nil;
429
430     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsPath.path]);
431     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsFilePath.path]);
432
433     [[NSFileManager defaultManager] removeItemAtURL:defaultResourceLoadStatisticsPath error:nil];
434 }
435
436 TEST(WebKit, WebsiteDataStoreEphemeralViaConfiguration)
437 {
438     RetainPtr<WebsiteDataStoreCustomPathsMessageHandler> handler = adoptNS([[WebsiteDataStoreCustomPathsMessageHandler alloc] init]);
439     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
440     [[configuration userContentController] addScriptMessageHandler:handler.get() name:@"testHandler"];
441
442     NSURL *defaultResourceLoadStatisticsPath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/WebsiteData/ResourceLoadStatistics/" stringByExpandingTildeInPath] isDirectory:YES];
443
444     [[NSFileManager defaultManager] removeItemAtURL:defaultResourceLoadStatisticsPath error:nil];
445
446     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsPath.path]);
447
448     RetainPtr<_WKWebsiteDataStoreConfiguration> dataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
449     configuration.get().websiteDataStore = [[[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration.get()] autorelease];
450     [configuration.get().websiteDataStore _setResourceLoadStatisticsEnabled:YES];
451
452     // We expect the directory to be created by starting up the data store machinery, but not the data file.
453     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsPath.path]);
454
455     NSURL *defaultResourceLoadStatisticsFilePath = [NSURL fileURLWithPath:[@"~/Library/WebKit/TestWebKitAPI/WebsiteData/ResourceLoadStatistics/full_browsing_session_resourceLog.plist" stringByExpandingTildeInPath] isDirectory:NO];
456     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsFilePath.path]);
457
458     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
459
460     NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"WebsiteDataStoreCustomPaths" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
461     [webView loadRequest:request];
462
463     [[[webView configuration] processPool] _syncNetworkProcessCookies];
464
465     // Forcibly shut down everything of WebKit that we can.
466     auto pid = [webView _webProcessIdentifier];
467     if (pid)
468         kill(pid, SIGKILL);
469
470     webView = nil;
471     handler = nil;
472     configuration = nil;
473
474     EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsPath.path]);
475     EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:defaultResourceLoadStatisticsFilePath.path]);
476
477     [[NSFileManager defaultManager] removeItemAtURL:defaultResourceLoadStatisticsPath error:nil];
478 }
479
480 TEST(WebKit, DoLoadWithNonDefaultDataStoreAfterTerminatingNetworkProcess)
481 {
482     auto websiteDataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
483
484     auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
485     webViewConfiguration.get().websiteDataStore = [[[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()] autorelease];
486
487     auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
488
489     NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
490     [webView loadRequest:request];
491     [webView _test_waitForDidFinishNavigation];
492
493     TestWebKitAPI::Util::spinRunLoop(1);
494
495     [webViewConfiguration.get().processPool _terminateNetworkProcess];
496
497     request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
498     [webView loadRequest:request];
499     [webView _test_waitForDidFinishNavigation];
500 }
501
502 TEST(WebKit, ApplicationIdentifiers)
503 {
504     auto websiteDataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
505     [websiteDataStoreConfiguration setSourceApplicationBundleIdentifier:@"testidentifier"];
506
507     auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
508     auto websiteDataStore = [[[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()] autorelease];
509     EXPECT_TRUE([websiteDataStore._sourceApplicationBundleIdentifier isEqualToString:@"testidentifier"]);
510     [websiteDataStore _setSourceApplicationBundleIdentifier:@"otheridentifier"];
511
512     [webViewConfiguration setWebsiteDataStore:websiteDataStore];
513     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
514     [webView synchronouslyLoadTestPageNamed:@"simple"];
515     
516     RetainPtr<NSException> exception;
517     @try {
518         [websiteDataStore _setSourceApplicationBundleIdentifier:@"settingShouldFailNow"];
519     } @catch(NSException *caught) {
520         exception = caught;
521     }
522     EXPECT_TRUE([[exception reason] isEqualToString:@"_setSourceApplicationBundleIdentifier cannot be called after networking has begun"]);
523     EXPECT_TRUE([websiteDataStore._sourceApplicationBundleIdentifier isEqualToString:@"otheridentifier"]);
524     EXPECT_TRUE([[websiteDataStoreConfiguration sourceApplicationBundleIdentifier] isEqualToString:@"testidentifier"]);
525 }
526
527 TEST(WebKit, NetworkCacheDirectory)
528 {
529     using namespace TestWebKitAPI;
530     TCPServer server([] (int socket) {
531         TCPServer::read(socket);
532         const char* response =
533         "HTTP/1.1 200 OK\r\n"
534         "Cache-Control: max-age=1000000\r\n"
535         "Content-Length: 6\r\n\r\n"
536         "Hello!";
537         TCPServer::write(socket, response, strlen(response));
538     });
539     
540     NSURL *tempDir = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"CustomPathsTest"] isDirectory:YES];
541     
542     auto websiteDataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
543     [websiteDataStoreConfiguration setNetworkCacheDirectory:tempDir];
544
545     auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
546     [webViewConfiguration setWebsiteDataStore:[[[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()] autorelease]];
547
548     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
549     [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/", server.port()]]]];
550     NSString *path = tempDir.path;
551     NSFileManager *fileManager = [NSFileManager defaultManager];
552     while (![fileManager fileExistsAtPath:path])
553         Util::spinRunLoop();
554     NSError *error = nil;
555     [fileManager removeItemAtPath:path error:&error];
556     EXPECT_FALSE(error);
557 }
558
559 TEST(WebKit, ApplicationCacheDirectories)
560 {
561     using namespace TestWebKitAPI;
562     TCPServer server([] (int socket) {
563         TCPServer::read(socket);
564         const char* firstResponse =
565         "HTTP/1.1 200 OK\r\n"
566         "Content-Length: 31\r\n\r\n"
567         "<html manifest='test.appcache'>";
568         TCPServer::write(socket, firstResponse, strlen(firstResponse));
569         
570         TCPServer::read(socket);
571         const char* secondResponse =
572         "HTTP/1.1 200 OK\r\n"
573         "Content-Length: 35\r\n\r\n"
574         "CACHE MANIFEST\n"
575         "index.html\n"
576         "test.mp4\n";
577         TCPServer::write(socket, secondResponse, strlen(secondResponse));
578
579         TCPServer::read(socket);
580         TCPServer::write(socket, firstResponse, strlen(firstResponse));
581         
582         const char* videoResponse =
583         "HTTP/1.1 200 OK\r\n"
584         "Content-Type: video/test\r\n"
585         "Content-Length: 5\r\n\r\n"
586         "test!";
587         TCPServer::read(socket);
588         TCPServer::write(socket, videoResponse, strlen(videoResponse));
589     });
590     
591     NSURL *tempDir = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"CustomPathsTest"] isDirectory:YES];
592     NSString *path = tempDir.path;
593     NSString *subdirectoryPath = [path stringByAppendingPathComponent:@"testsubdirectory"];
594     NSFileManager *fileManager = [NSFileManager defaultManager];
595     EXPECT_FALSE([fileManager fileExistsAtPath:subdirectoryPath]);
596
597     auto websiteDataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
598
599     [websiteDataStoreConfiguration setApplicationCacheDirectory:tempDir];
600     [websiteDataStoreConfiguration setApplicationCacheFlatFileSubdirectoryName:@"testsubdirectory"];
601     
602     auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
603     [webViewConfiguration setWebsiteDataStore:[[[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()] autorelease]];
604     
605     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
606     [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/index.html", server.port()]]]];
607
608     while (![fileManager fileExistsAtPath:subdirectoryPath])
609         Util::spinRunLoop();
610
611     NSError *error = nil;
612     [fileManager removeItemAtPath:path error:&error];
613     EXPECT_FALSE(error);
614 }
615
616 // FIXME: investigate why this test times out on High Sierra
617 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || PLATFORM(IOS_FAMILY)
618 TEST(WebKit, MediaCache)
619 {
620     std::atomic<bool> done = false;
621     using namespace TestWebKitAPI;
622     RetainPtr<NSData> data = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"test" withExtension:@"mp4" subdirectory:@"TestWebKitAPI.resources"]];
623     uint64_t dataLength = [data length];
624
625     TCPServer server([&] (int socket) {
626         TCPServer::read(socket);
627         const char* firstResponse =
628         "HTTP/1.1 200 OK\r\n"
629         "Content-Type: text/html\r\n"
630         "Content-Length: 55\r\n\r\n"
631         "<video><source src='test.mp4' type='video/mp4'></video>";
632         TCPServer::write(socket, firstResponse, strlen(firstResponse));
633
634         while (!done) {
635             auto bytes = TCPServer::read(socket);
636             if (done)
637                 break;
638             StringView request(static_cast<const LChar*>(bytes.data()), bytes.size());
639             String rangeBytes = "Range: bytes="_s;
640             auto begin = request.find(StringView(rangeBytes), 0);
641             ASSERT(begin != notFound);
642             auto dash = request.find('-', begin);
643             ASSERT(dash != notFound);
644             auto end = request.find('\r', dash);
645             ASSERT(end != notFound);
646
647             auto rangeBegin = *request.substring(begin + rangeBytes.length(), dash - begin - rangeBytes.length()).toUInt64Strict();
648             auto rangeEnd = *request.substring(dash + 1, end - dash - 1).toUInt64Strict();
649
650             NSString *responseHeaderString = [NSString stringWithFormat:
651                 @"HTTP/1.1 206 Partial Content\r\n"
652                 "Content-Range: bytes %llu-%llu/%llu\r\n"
653                 "Content-Length: %llu\r\n\r\n",
654                 rangeBegin, rangeEnd, dataLength, rangeEnd - rangeBegin];
655             NSData *responseHeader = [responseHeaderString dataUsingEncoding:NSUTF8StringEncoding];
656             NSData *responseBody = [data subdataWithRange:NSMakeRange(rangeBegin, rangeEnd - rangeBegin)];
657             NSMutableData *response = [NSMutableData dataWithCapacity:responseHeader.length + responseBody.length];
658             [response appendData:responseHeader];
659             [response appendData:responseBody];
660             TCPServer::write(socket, response.bytes, response.length);
661         }
662     });
663
664     NSURL *tempDir = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"CustomPathsTest"] isDirectory:YES];
665     NSString *path = tempDir.path;
666     NSFileManager *fileManager = [NSFileManager defaultManager];
667     EXPECT_FALSE([fileManager fileExistsAtPath:path]);
668
669     auto websiteDataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
670     [websiteDataStoreConfiguration setMediaCacheDirectory:tempDir];
671
672     auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
673     [webViewConfiguration setWebsiteDataStore:[[[WKWebsiteDataStore alloc] _initWithConfiguration:websiteDataStoreConfiguration.get()] autorelease]];
674
675     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
676     [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/", server.port()]]]];
677
678     NSError *error = nil;
679     while (![fileManager contentsOfDirectoryAtPath:path error:&error].count)
680         Util::spinRunLoop();
681     EXPECT_FALSE(error);
682
683     done = true;
684     [[webView configuration].processPool _terminateNetworkProcess];
685
686     [fileManager removeItemAtPath:path error:&error];
687     EXPECT_FALSE(error);
688 }
689 #endif