Reuse existing WebPageProxy quota handler for NetworkProcessProxy quota requests
[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 "TestWKWebView.h"
32 #import <WebKit/WKPreferencesPrivate.h>
33 #import <WebKit/WKProcessPoolPrivate.h>
34 #import <WebKit/WKURLSchemeHandler.h>
35 #import <WebKit/WKURLSchemeTaskPrivate.h>
36 #import <WebKit/WKWebViewConfigurationPrivate.h>
37 #import <WebKit/WKWebsiteDataStorePrivate.h>
38 #import <WebKit/WKWebsiteDataStoreRef.h>
39 #import <WebKit/_WKWebsiteDataStoreConfiguration.h>
40 #import <wtf/BlockPtr.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 using namespace TestWebKitAPI;
48
49 @interface QuotaDelegate : NSObject <WKUIDelegate>
50 -(bool)quotaDelegateCalled;
51 -(void)grantQuota;
52 -(void)denyQuota;
53 @end
54
55 static bool receivedQuotaDelegateCalled;
56
57 @implementation QuotaDelegate {
58     bool _quotaDelegateCalled;
59     unsigned long long _currentQuota;
60     unsigned long long _expectedUsage;
61     BlockPtr<void(unsigned long long newQuota)> _decisionHandler;
62 }
63
64 - (instancetype)init
65 {
66     if (!(self = [super init]))
67         return nil;
68
69     _quotaDelegateCalled = false;
70     _expectedUsage = 0;
71     _currentQuota = 0;
72     
73     return self;
74 }
75
76 - (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
77 {
78     receivedQuotaDelegateCalled = true;
79     _quotaDelegateCalled = true;
80     _currentQuota = currentQuota;
81     _expectedUsage = expectedUsage;
82     _decisionHandler = decisionHandler;
83 }
84
85 -(bool)quotaDelegateCalled {
86     return _quotaDelegateCalled;
87 }
88
89 -(void)grantQuota {
90     if (_quotaDelegateCalled)
91         _decisionHandler(_expectedUsage);
92     _quotaDelegateCalled = false;
93 }
94
95 -(void)denyQuota {
96     if (_quotaDelegateCalled)
97         _decisionHandler(_currentQuota);
98     _quotaDelegateCalled = false;
99 }
100
101 @end
102
103 struct ResourceInfo {
104     RetainPtr<NSString> mimeType;
105     const char* data;
106 };
107
108 @interface StorageSchemes : NSObject <WKURLSchemeHandler> {
109 @public
110     HashMap<String, ResourceInfo> resources;
111 }
112 @end
113
114 @implementation StorageSchemes {
115 }
116
117 - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task
118 {
119     auto entry = resources.find([task.request.URL absoluteString]);
120     if (entry == resources.end()) {
121         NSLog(@"Did not find resource entry for URL %@", task.request.URL);
122         return;
123     }
124     
125     RetainPtr<NSURLResponse> response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:entry->value.mimeType.get() expectedContentLength:1 textEncodingName:nil]);
126     [task didReceiveResponse:response.get()];
127     
128     [task didReceiveData:[NSData dataWithBytesNoCopy:(void*)entry->value.data length:strlen(entry->value.data) freeWhenDone:NO]];
129     [task didFinish];
130 }
131
132 - (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task
133 {
134 }
135
136 @end
137
138 static bool receivedMessage;
139
140 @interface QuotaMessageHandler : NSObject <WKScriptMessageHandler>
141 -(void)setExpectedMessage:(NSString *)message;
142 @end
143
144 @implementation QuotaMessageHandler {
145     NSString *_expectedMessage;
146 }
147
148 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
149 {
150     if (_expectedMessage) {
151         EXPECT_TRUE([[message body] isEqualToString:_expectedMessage]);
152         _expectedMessage = nil;
153     }
154     receivedMessage = true;
155 }
156
157 -(void)setExpectedMessage:(NSString *)message {
158     _expectedMessage = message;
159 }
160 @end
161
162 static const char* TestBytes = R"SWRESOURCE(
163 <script>
164
165 async function doTest()
166 {
167     const cache = await window.caches.open("mycache");
168     const promise = cache.put("http://example.org/test", new Response(new ArrayBuffer(1024 * 500)));
169     window.webkit.messageHandlers.qt.postMessage("start");
170     promise.then(() => {
171         window.webkit.messageHandlers.qt.postMessage("pass");
172     }, () => {
173         window.webkit.messageHandlers.qt.postMessage("fail");
174     });
175 }
176 doTest();
177
178 function doTestAgain()
179 {
180     doTest();
181 }
182 </script>
183 )SWRESOURCE";
184
185 static bool done;
186
187 static inline void setVisible(TestWKWebView *webView)
188 {
189 #if PLATFORM(MAC)
190     [webView.window setIsVisible:YES];
191 #else
192     webView.window.hidden = NO;
193 #endif
194 }
195
196 TEST(WebKit, QuotaDelegate)
197 {
198     done = false;
199     [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
200         done = true;
201     }];
202     TestWebKitAPI::Util::run(&done);
203
204     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
205
206     [[configuration websiteDataStore] _setPerOriginStorageQuota: 1024 * 400];
207     
208     auto messageHandler = adoptNS([[QuotaMessageHandler alloc] init]);
209     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"qt"];
210
211     auto handler1 = adoptNS([[StorageSchemes alloc] init]);
212     handler1->resources.set("qt1://test1.html", ResourceInfo { @"text/html", TestBytes });
213     [configuration setURLSchemeHandler:handler1.get() forURLScheme:@"QT1"];
214     [configuration.get().processPool _registerURLSchemeServiceWorkersCanHandle:@"qt1"];
215
216     auto handler2 = adoptNS([[StorageSchemes alloc] init]);
217     handler2->resources.set("qt2://test2.html", ResourceInfo { @"text/html", TestBytes });
218     [configuration setURLSchemeHandler:handler2.get() forURLScheme:@"QT2"];
219     [configuration.get().processPool _registerURLSchemeServiceWorkersCanHandle:@"qt2"];
220
221     auto webView1 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
222     auto delegate1 = adoptNS([[QuotaDelegate alloc] init]);
223     [webView1 setUIDelegate:delegate1.get()];
224     setVisible(webView1.get());
225     
226     receivedQuotaDelegateCalled = false;
227     [webView1 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt1://test1.html"]]];
228     Util::run(&receivedQuotaDelegateCalled);
229
230     auto webView2 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
231     auto delegate2 = adoptNS([[QuotaDelegate alloc] init]);
232     [webView2 setUIDelegate:delegate2.get()];
233     setVisible(webView2.get());
234
235     receivedMessage = false;
236     [webView2 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt2://test2.html"]]];
237     [messageHandler setExpectedMessage: @"start"];
238     Util::run(&receivedMessage);
239
240     EXPECT_FALSE(delegate2.get().quotaDelegateCalled);
241     [delegate1 grantQuota];
242
243     [messageHandler setExpectedMessage: @"pass"];
244     receivedMessage = false;
245     Util::run(&receivedMessage);
246
247     while (!delegate2.get().quotaDelegateCalled)
248         TestWebKitAPI::Util::sleep(0.1);
249
250     [delegate2 denyQuota];
251
252     [messageHandler setExpectedMessage: @"fail"];
253     receivedMessage = false;
254     Util::run(&receivedMessage);
255 }
256
257 TEST(WebKit, QuotaDelegateReload)
258 {
259     done = false;
260     [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
261         done = true;
262     }];
263     TestWebKitAPI::Util::run(&done);
264     
265     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
266     
267     [[configuration websiteDataStore] _setPerOriginStorageQuota: 1024 * 400];
268     
269     auto messageHandler = adoptNS([[QuotaMessageHandler alloc] init]);
270     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"qt"];
271     
272     auto handler = adoptNS([[StorageSchemes alloc] init]);
273     handler->resources.set("qt://test1.html", ResourceInfo { @"text/html", TestBytes });
274     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"QT"];
275     [configuration.get().processPool _registerURLSchemeServiceWorkersCanHandle:@"qt"];
276     
277     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
278     auto delegate = adoptNS([[QuotaDelegate alloc] init]);
279     [webView setUIDelegate:delegate.get()];
280     setVisible(webView.get());
281
282     receivedQuotaDelegateCalled = false;
283     [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt://test1.html"]]];
284     Util::run(&receivedQuotaDelegateCalled);
285
286     [delegate denyQuota];
287
288     [messageHandler setExpectedMessage: @"fail"];
289     receivedMessage = false;
290     Util::run(&receivedMessage);
291
292     receivedQuotaDelegateCalled = false;
293     [webView reload];
294     Util::run(&receivedQuotaDelegateCalled);
295
296     [delegate grantQuota];
297
298     [messageHandler setExpectedMessage: @"pass"];
299     receivedMessage = false;
300     Util::run(&receivedMessage);
301 }
302
303 TEST(WebKit, QuotaDelegateNavigateFragment)
304 {
305     done = false;
306     [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
307         done = true;
308     }];
309     TestWebKitAPI::Util::run(&done);
310
311     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
312
313     [[configuration websiteDataStore] _setPerOriginStorageQuota: 1024 * 400];
314
315     auto messageHandler = adoptNS([[QuotaMessageHandler alloc] init]);
316     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"qt"];
317
318     auto handler = adoptNS([[StorageSchemes alloc] init]);
319     handler->resources.set("qt://test1.html", ResourceInfo { @"text/html", TestBytes });
320     [configuration setURLSchemeHandler:handler.get() forURLScheme:@"QT"];
321     [configuration.get().processPool _registerURLSchemeServiceWorkersCanHandle:@"qt"];
322
323     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
324     auto delegate = adoptNS([[QuotaDelegate alloc] init]);
325     [webView setUIDelegate:delegate.get()];
326     setVisible(webView.get());
327
328     receivedQuotaDelegateCalled = false;
329     [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt://test1.html"]]];
330     Util::run(&receivedQuotaDelegateCalled);
331
332     [delegate denyQuota];
333
334     [messageHandler setExpectedMessage: @"fail"];
335     receivedMessage = false;
336     Util::run(&receivedMessage);
337
338     receivedQuotaDelegateCalled = false;
339     [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt://test1.html#fragment"]]];
340     [webView stringByEvaluatingJavaScript:@"doTestAgain()"];
341
342     [messageHandler setExpectedMessage: @"start"];
343     receivedMessage = false;
344     Util::run(&receivedMessage);
345
346     [messageHandler setExpectedMessage: @"fail"];
347     receivedMessage = false;
348     Util::run(&receivedMessage);
349
350     EXPECT_FALSE(receivedQuotaDelegateCalled);
351 }