Move WKProcessPool._registerURLSchemeServiceWorkersCanHandle to _WKWebsiteDataStoreCo...
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / StorageQuota.mm
1 /*
2  * Copyright (C) 2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import <WebKit/WebKit.h>
28
29 #import "PlatformUtilities.h"
30 #import "Test.h"
31 #import "TestNavigationDelegate.h"
32 #import "TestWKWebView.h"
33 #import <WebKit/WKPreferencesPrivate.h>
34 #import <WebKit/WKProcessPoolPrivate.h>
35 #import <WebKit/WKURLSchemeHandler.h>
36 #import <WebKit/WKURLSchemeTaskPrivate.h>
37 #import <WebKit/WKWebViewConfigurationPrivate.h>
38 #import <WebKit/WKWebsiteDataStorePrivate.h>
39 #import <WebKit/WKWebsiteDataStoreRef.h>
40 #import <WebKit/_WKWebsiteDataStoreConfiguration.h>
41 #import <wtf/BlockPtr.h>
42 #import <wtf/HashMap.h>
43 #import <wtf/RetainPtr.h>
44 #import <wtf/Vector.h>
45 #include <wtf/text/StringConcatenateNumbers.h>
46 #import <wtf/text/StringHash.h>
47 #import <wtf/text/WTFString.h>
48
49 using namespace TestWebKitAPI;
50
51 static bool didFinishNavigation;
52
53 @interface QuotaDelegate : NSObject <WKUIDelegate>
54 -(bool)quotaDelegateCalled;
55 -(void)grantQuota;
56 -(void)denyQuota;
57 @end
58
59 static bool receivedQuotaDelegateCalled;
60
61 @implementation QuotaDelegate {
62     bool _quotaDelegateCalled;
63     unsigned long long _currentQuota;
64     unsigned long long _expectedUsage;
65     BlockPtr<void(unsigned long long newQuota)> _decisionHandler;
66 }
67
68 - (instancetype)init
69 {
70     if (!(self = [super init]))
71         return nil;
72
73     _quotaDelegateCalled = false;
74     _expectedUsage = 0;
75     _currentQuota = 0;
76     
77     return self;
78 }
79
80 - (void)_webView:(WKWebView *)webView decideDatabaseQuotaForSecurityOrigin:(WKSecurityOrigin *)securityOrigin currentQuota:(unsigned long long)currentQuota currentOriginUsage:(unsigned long long)currentOriginUsage currentDatabaseUsage:(unsigned long long)currentUsage expectedUsage:(unsigned long long)expectedUsage decisionHandler:(void (^)(unsigned long long newQuota))decisionHandler
81 {
82     receivedQuotaDelegateCalled = true;
83     _quotaDelegateCalled = true;
84     _currentQuota = currentQuota;
85     _expectedUsage = expectedUsage;
86     _decisionHandler = decisionHandler;
87 }
88
89 -(bool)quotaDelegateCalled {
90     return _quotaDelegateCalled;
91 }
92
93 -(void)grantQuota {
94     if (_quotaDelegateCalled)
95         _decisionHandler(_expectedUsage);
96     _quotaDelegateCalled = false;
97 }
98
99 -(void)denyQuota {
100     if (_quotaDelegateCalled)
101         _decisionHandler(_currentQuota);
102     _quotaDelegateCalled = false;
103 }
104
105 @end
106
107 struct ResourceInfo {
108     RetainPtr<NSString> mimeType;
109     const char* data;
110 };
111
112 @interface StorageSchemes : NSObject <WKURLSchemeHandler> {
113 @public
114     HashMap<String, ResourceInfo> resources;
115 }
116 @end
117
118 @implementation StorageSchemes {
119 }
120
121 - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task
122 {
123     auto entry = resources.find([task.request.URL absoluteString]);
124     if (entry == resources.end()) {
125         NSLog(@"Did not find resource entry for URL %@", task.request.URL);
126         return;
127     }
128     
129     RetainPtr<NSURLResponse> response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:entry->value.mimeType.get() expectedContentLength:1 textEncodingName:nil]);
130     [task didReceiveResponse:response.get()];
131     
132     [task didReceiveData:[NSData dataWithBytesNoCopy:(void*)entry->value.data length:strlen(entry->value.data) freeWhenDone:NO]];
133     [task didFinish];
134 }
135
136 - (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task
137 {
138 }
139
140 @end
141
142 static bool receivedMessage;
143
144 @interface QuotaMessageHandler : NSObject <WKScriptMessageHandler>
145 -(void)setExpectedMessage:(NSString *)message;
146 @end
147
148 @implementation QuotaMessageHandler {
149     NSString *_expectedMessage;
150 }
151
152 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
153 {
154     if (_expectedMessage) {
155         EXPECT_TRUE([[message body] isEqualToString:_expectedMessage]);
156         _expectedMessage = nil;
157     }
158     receivedMessage = true;
159 }
160
161 -(void)setExpectedMessage:(NSString *)message {
162     _expectedMessage = message;
163 }
164 @end
165
166 static const char* TestBytes = R"SWRESOURCE(
167 <script>
168
169 async function doTest()
170 {
171     const cache = await window.caches.open("mycache");
172     const promise = cache.put("http://example.org/test", new Response(new ArrayBuffer(1024 * 500)));
173     window.webkit.messageHandlers.qt.postMessage("start");
174     promise.then(() => {
175         window.webkit.messageHandlers.qt.postMessage("pass");
176     }, () => {
177         window.webkit.messageHandlers.qt.postMessage("fail");
178     });
179 }
180 doTest();
181
182 function doTestAgain()
183 {
184     doTest();
185 }
186 </script>
187 )SWRESOURCE";
188
189 static const char* TestUrlBytes = R"SWRESOURCE(
190 <script>
191
192 var index = 0;
193 async function test(num)
194 {
195     index++;
196     url = "http://example.org/test" + index;
197
198     const cache = await window.caches.open("mycache");
199     const promise = cache.put(url, new Response(new ArrayBuffer(num * 1024 * 1024)));
200     promise.then(() => {
201         window.webkit.messageHandlers.qt.postMessage("pass");
202     }, () => {
203         window.webkit.messageHandlers.qt.postMessage("fail");
204     });
205 }
206
207 function doTest(num)
208 {
209     test(num);
210 }
211 </script>
212 )SWRESOURCE";
213
214 static bool done;
215
216 static inline void setVisible(TestWKWebView *webView)
217 {
218 #if PLATFORM(MAC)
219     [webView.window setIsVisible:YES];
220 #else
221     webView.window.hidden = NO;
222 #endif
223 }
224
225 TEST(WebKit, QuotaDelegate)
226 {
227     done = false;
228     _WKWebsiteDataStoreConfiguration *storeConfiguration = [[[_WKWebsiteDataStoreConfiguration alloc] init] autorelease];
229     storeConfiguration.perOriginStorageQuota = 1024 * 400;
230     [storeConfiguration registerURLSchemeServiceWorkersCanHandleForTesting:@"qt1"];
231     [storeConfiguration registerURLSchemeServiceWorkersCanHandleForTesting:@"qt2"];
232     WKWebsiteDataStore *dataStore = [[[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration] autorelease];
233     [dataStore removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
234         done = true;
235     }];
236     TestWebKitAPI::Util::run(&done);
237
238     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
239     [configuration setWebsiteDataStore:dataStore];
240
241     auto messageHandler = adoptNS([[QuotaMessageHandler alloc] init]);
242     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"qt"];
243
244     auto handler1 = adoptNS([[StorageSchemes alloc] init]);
245     handler1->resources.set("qt1://test1.html", ResourceInfo { @"text/html", TestBytes });
246     [configuration setURLSchemeHandler:handler1.get() forURLScheme:@"QT1"];
247
248     auto handler2 = adoptNS([[StorageSchemes alloc] init]);
249     handler2->resources.set("qt2://test2.html", ResourceInfo { @"text/html", TestBytes });
250     [configuration setURLSchemeHandler:handler2.get() forURLScheme:@"QT2"];
251
252     auto webView1 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
253     auto delegate1 = adoptNS([[QuotaDelegate alloc] init]);
254     [webView1 setUIDelegate:delegate1.get()];
255     setVisible(webView1.get());
256     
257     receivedQuotaDelegateCalled = false;
258     [webView1 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt1://test1.html"]]];
259     Util::run(&receivedQuotaDelegateCalled);
260
261     auto webView2 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
262     auto delegate2 = adoptNS([[QuotaDelegate alloc] init]);
263     [webView2 setUIDelegate:delegate2.get()];
264     setVisible(webView2.get());
265
266     receivedMessage = false;
267     [webView2 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt2://test2.html"]]];
268     [messageHandler setExpectedMessage: @"start"];
269     Util::run(&receivedMessage);
270
271     EXPECT_FALSE(delegate2.get().quotaDelegateCalled);
272     [delegate1 grantQuota];
273
274     [messageHandler setExpectedMessage: @"pass"];
275     receivedMessage = false;
276     Util::run(&receivedMessage);
277
278     while (!delegate2.get().quotaDelegateCalled)
279         TestWebKitAPI::Util::sleep(0.1);
280
281     [delegate2 denyQuota];
282
283     [messageHandler setExpectedMessage: @"fail"];
284     receivedMessage = false;
285     Util::run(&receivedMessage);
286 }
287
288 TEST(WebKit, QuotaDelegateReload)
289 {
290     done = false;
291     _WKWebsiteDataStoreConfiguration *storeConfiguration = [[[_WKWebsiteDataStoreConfiguration alloc] init] autorelease];
292     storeConfiguration.perOriginStorageQuota = 1024 * 400;
293     [storeConfiguration registerURLSchemeServiceWorkersCanHandleForTesting:@"qt"];
294     WKWebsiteDataStore *dataStore = [[[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration] autorelease];
295     [dataStore removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
296         done = true;
297     }];
298     TestWebKitAPI::Util::run(&done);
299     
300     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
301     [configuration setWebsiteDataStore:dataStore];
302     
303     auto messageHandler = adoptNS([[QuotaMessageHandler alloc] init]);
304     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"qt"];
305     
306     auto handler = adoptNS([[StorageSchemes alloc] init]);
307     handler->resources.set("qt://test1.html", ResourceInfo { @"text/html", TestBytes });
308     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"QT"];
309     
310     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
311     auto delegate = adoptNS([[QuotaDelegate alloc] init]);
312     [webView setUIDelegate:delegate.get()];
313     setVisible(webView.get());
314
315     receivedQuotaDelegateCalled = false;
316     [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt://test1.html"]]];
317     Util::run(&receivedQuotaDelegateCalled);
318
319     [delegate denyQuota];
320
321     [messageHandler setExpectedMessage: @"fail"];
322     receivedMessage = false;
323     Util::run(&receivedMessage);
324
325     receivedQuotaDelegateCalled = false;
326     [webView reload];
327     Util::run(&receivedQuotaDelegateCalled);
328
329     [delegate grantQuota];
330
331     [messageHandler setExpectedMessage: @"pass"];
332     receivedMessage = false;
333     Util::run(&receivedMessage);
334 }
335
336 TEST(WebKit, QuotaDelegateNavigateFragment)
337 {
338     done = false;
339     _WKWebsiteDataStoreConfiguration *storeConfiguration = [[[_WKWebsiteDataStoreConfiguration alloc] init] autorelease];
340     [storeConfiguration registerURLSchemeServiceWorkersCanHandleForTesting:@"qt"];
341     storeConfiguration.perOriginStorageQuota = 1024 * 400;
342     WKWebsiteDataStore *dataStore = [[[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration] autorelease];
343     [dataStore removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
344         done = true;
345     }];
346     TestWebKitAPI::Util::run(&done);
347
348     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
349     [configuration setWebsiteDataStore:dataStore];
350
351     auto messageHandler = adoptNS([[QuotaMessageHandler alloc] init]);
352     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"qt"];
353
354     auto handler = adoptNS([[StorageSchemes alloc] init]);
355     handler->resources.set("qt://test1.html", ResourceInfo { @"text/html", TestBytes });
356     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"QT"];
357
358     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
359     auto delegate = adoptNS([[QuotaDelegate alloc] init]);
360     [webView setUIDelegate:delegate.get()];
361     setVisible(webView.get());
362
363     receivedQuotaDelegateCalled = false;
364     [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt://test1.html"]]];
365     Util::run(&receivedQuotaDelegateCalled);
366
367     [delegate denyQuota];
368
369     [messageHandler setExpectedMessage: @"fail"];
370     receivedMessage = false;
371     Util::run(&receivedMessage);
372
373     receivedQuotaDelegateCalled = false;
374     [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt://test1.html#fragment"]]];
375     [webView stringByEvaluatingJavaScript:@"doTestAgain()"];
376
377     [messageHandler setExpectedMessage: @"start"];
378     receivedMessage = false;
379     Util::run(&receivedMessage);
380
381     [messageHandler setExpectedMessage: @"fail"];
382     receivedMessage = false;
383     Util::run(&receivedMessage);
384
385     EXPECT_FALSE(receivedQuotaDelegateCalled);
386 }
387
388 TEST(WebKit, DefaultQuota)
389 {
390     done = false;
391     _WKWebsiteDataStoreConfiguration *storeConfiguration = [[[_WKWebsiteDataStoreConfiguration alloc] init] autorelease];
392     [storeConfiguration registerURLSchemeServiceWorkersCanHandleForTesting:@"qt"];
393     WKWebsiteDataStore *dataStore = [[[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration] autorelease];
394
395     [dataStore removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
396         done = true;
397     }];
398     TestWebKitAPI::Util::run(&done);
399
400     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
401     [configuration setWebsiteDataStore:dataStore];
402
403     auto messageHandler = adoptNS([[QuotaMessageHandler alloc] init]);
404     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"qt"];
405
406     auto handler = adoptNS([[StorageSchemes alloc] init]);
407     handler->resources.set("qt://test1.html", ResourceInfo { @"text/html", TestUrlBytes });
408     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"QT"];
409
410     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
411     auto delegate = adoptNS([[QuotaDelegate alloc] init]);
412     [webView setUIDelegate:delegate.get()];
413     setVisible(webView.get());
414
415     auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
416     [navigationDelegate setDidFinishNavigation:^(WKWebView *, WKNavigation *) {
417         didFinishNavigation = true;
418     }];
419     [webView setNavigationDelegate:navigationDelegate.get()];
420
421     didFinishNavigation = false;
422     [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt://test1.html"]]];
423     Util::run(&didFinishNavigation);
424
425     receivedQuotaDelegateCalled = false;
426
427     // Storing 10 entries of 10 MB should not hit the default quota which is 1GB
428     for (int i = 0; i < 10; ++i) {
429         [webView stringByEvaluatingJavaScript:makeString("doTest(10)")];
430         [messageHandler setExpectedMessage: @"pass"];
431         receivedMessage = false;
432         Util::run(&receivedMessage);
433     }
434     EXPECT_FALSE(receivedQuotaDelegateCalled);
435 }