source: webkit/trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm

Last change on this file was 295694, checked in by J Pascoe, 3 years ago

NotificationEventEnabled should be enabled macOS Ventura+
https://bugs.webkit.org/show_bug.cgi?id=241605
rdar://94441142

Reviewed by Alex Christensen.

  • Source/WTF/wtf/PlatformEnable.h:

This API should only be enabled on macOS 13 and later.

  • LayoutTests/TestExpectations:
  • LayoutTests/platform/mac-wk2/TestExpectations:
  • Tools/TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm:
  • Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm:

Update test expectations to account for these being macOS Ventura+

Canonical link: https://commits.webkit.org/251699@main

File size: 29.5 KB
Line 
1/*
2 * Copyright (C) 2021 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#if ENABLE(NOTIFICATIONS) && ENABLE(NOTIFICATION_EVENT)
29
30#import "DeprecatedGlobalValues.h"
31#import "HTTPServer.h"
32#import "PlatformUtilities.h"
33#import "Test.h"
34#import "TestNotificationProvider.h"
35#import "TestWKWebView.h"
36#import <WebCore/RegistrationDatabase.h>
37#import <WebKit/WKNotificationProvider.h>
38#import <WebKit/WKProcessPoolPrivate.h>
39#import <WebKit/WKWebViewPrivate.h>
40#import <WebKit/WKWebsiteDataStorePrivate.h>
41#import <WebKit/_WKWebsiteDataStoreConfiguration.h>
42#import <wtf/HexNumber.h>
43
44static NSDictionary *messageDictionary(NSData *data, NSURL *registration)
45{
46 return @{
47 @"WebKitPushData" : data,
48 @"WebKitPushRegistrationURL" : registration
49 };
50}
51
52static String expectedMessage;
53
54@interface PushAPIMessageHandlerWithExpectedMessage : NSObject <WKScriptMessageHandler>
55@end
56
57@implementation PushAPIMessageHandlerWithExpectedMessage
58- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
59{
60 EXPECT_WK_STREQ(message.body, expectedMessage);
61 done = true;
62}
63@end
64
65// FIXME: Update the test to do subscription before pushing message.
66static constexpr auto mainBytes = R"SWRESOURCE(
67<script>
68function log(msg)
69{
70 window.webkit.messageHandlers.sw.postMessage(msg);
71}
72
73const channel = new MessageChannel();
74channel.port1.onmessage = (event) => log(event.data);
75
76navigator.serviceWorker.register('/sw.js').then((registration) => {
77 if (registration.active) {
78 registration.active.postMessage({port: channel.port2}, [channel.port2]);
79 return;
80 }
81 worker = registration.installing;
82 worker.addEventListener('statechange', function() {
83 if (worker.state == 'activated')
84 worker.postMessage({port: channel.port2}, [channel.port2]);
85 });
86}).catch(function(error) {
87 log("Registration failed with: " + error);
88});
89</script>
90)SWRESOURCE"_s;
91
92static constexpr auto scriptBytes = R"SWRESOURCE(
93let port;
94self.addEventListener("message", (event) => {
95 port = event.data.port;
96 port.postMessage("Ready");
97});
98self.addEventListener("push", (event) => {
99 self.registration.showNotification("notification");
100 try {
101 if (!event.data) {
102 port.postMessage("Received: null data");
103 return;
104 }
105 const value = event.data.text();
106 port.postMessage("Received: " + value);
107 if (value != 'Sweet Potatoes')
108 event.waitUntil(Promise.reject('I want sweet potatoes'));
109 } catch (e) {
110 port.postMessage("Got exception " + e);
111 }
112});
113)SWRESOURCE"_s;
114
115static void clearWebsiteDataStore(WKWebsiteDataStore *store)
116{
117 __block bool clearedStore = false;
118 [store removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
119 clearedStore = true;
120 }];
121 TestWebKitAPI::Util::run(&clearedStore);
122}
123
124static bool pushMessageProcessed = false;
125static bool pushMessageSuccessful = false;
126
127static bool waitUntilEvaluatesToTrue(const Function<bool()>& f)
128{
129 unsigned timeout = 0;
130 do {
131 if (f())
132 return true;
133 TestWebKitAPI::Util::sleep(0.1);
134 } while (++timeout < 100);
135 return false;
136}
137
138TEST(PushAPI, firePushEvent)
139{
140 TestWebKitAPI::HTTPServer server({
141 { "/"_s, { mainBytes } },
142 { "/sw.js"_s, { {{ "Content-Type"_s, "application/javascript"_s }}, scriptBytes } }
143 }, TestWebKitAPI::HTTPServer::Protocol::Http);
144
145 [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
146
147 auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
148
149 auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
150 provider.setPermission(server.origin(), true);
151
152 auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
153 [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
154
155 clearWebsiteDataStore([configuration websiteDataStore]);
156
157 expectedMessage = "Ready"_s;
158 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
159 [webView loadRequest:server.request()];
160
161 TestWebKitAPI::Util::run(&done);
162
163 done = false;
164 pushMessageProcessed = false;
165 pushMessageSuccessful = false;
166 NSString *message = @"Sweet Potatoes";
167 expectedMessage = "Received: Sweet Potatoes"_s;
168
169 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
170 pushMessageSuccessful = result;
171 pushMessageProcessed = true;
172 }];
173 TestWebKitAPI::Util::run(&done);
174
175 TestWebKitAPI::Util::run(&pushMessageProcessed);
176 EXPECT_TRUE(pushMessageSuccessful);
177
178 done = false;
179 pushMessageProcessed = false;
180 pushMessageSuccessful = false;
181 message = @"Rotten Potatoes";
182 expectedMessage = "Received: Rotten Potatoes"_s;
183 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
184 pushMessageSuccessful = result;
185 pushMessageProcessed = true;
186 }];
187 TestWebKitAPI::Util::run(&done);
188
189 TestWebKitAPI::Util::run(&pushMessageProcessed);
190 EXPECT_FALSE(pushMessageSuccessful);
191
192 clearWebsiteDataStore([configuration websiteDataStore]);
193}
194
195static constexpr auto waitOneSecondScriptBytes = R"SWRESOURCE(
196let port;
197self.addEventListener("message", (event) => {
198 port = event.data.port;
199 port.postMessage("Ready");
200});
201self.addEventListener("push", (event) => {
202 self.registration.showNotification("notification");
203 if (!event.data)
204 return;
205 const value = event.data.text();
206 if (value === 'Sweet Potatoes')
207 event.waitUntil(new Promise(resolve => setTimeout(resolve, 1000)));
208 else if (value === 'Rotten Potatoes')
209 event.waitUntil(new Promise((resolve, reject) => setTimeout(reject, 1000)));
210 else if (value === 'Timeless Potatoes')
211 event.waitUntil(new Promise(resolve => { }));
212});
213)SWRESOURCE"_s;
214
215static void terminateNetworkProcessWhileRegistrationIsStored(WKWebViewConfiguration *configuration)
216{
217 auto path = configuration.websiteDataStore._configuration._serviceWorkerRegistrationDirectory.path;
218 NSURL* directory = [NSURL fileURLWithPath:path isDirectory:YES];
219 auto filename = makeString("ServiceWorkerRegistrations-"_s, WebCore::RegistrationDatabase::schemaVersion, ".sqlite3");
220 NSURL *swDBPath = [directory URLByAppendingPathComponent:filename];
221 unsigned timeout = 0;
222 while (![[NSFileManager defaultManager] fileExistsAtPath:swDBPath.path] && ++timeout < 100)
223 TestWebKitAPI::Util::sleep(0.1);
224 // Let's close the SQL database.
225 [configuration.websiteDataStore _sendNetworkProcessWillSuspendImminently];
226 [configuration.websiteDataStore _terminateNetworkProcess];
227}
228
229TEST(PushAPI, firePushEventWithNoPagesSuccessful)
230{
231 TestWebKitAPI::HTTPServer server({
232 { "/"_s, { mainBytes } },
233 { "/sw.js"_s, { {{ "Content-Type"_s, "application/javascript"_s }}, waitOneSecondScriptBytes } }
234 }, TestWebKitAPI::HTTPServer::Protocol::Http);
235
236 [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
237
238 auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
239 clearWebsiteDataStore([configuration websiteDataStore]);
240
241 auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
242 provider.setPermission(server.origin(), true);
243
244 auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
245 [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
246
247 expectedMessage = "Ready"_s;
248 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
249 [webView loadRequest:server.request()];
250
251 TestWebKitAPI::Util::run(&done);
252
253 [webView _close];
254 webView = nullptr;
255
256 terminateNetworkProcessWhileRegistrationIsStored(configuration.get());
257
258 // Push event for service worker without any related page, should succeed.
259 pushMessageProcessed = false;
260 pushMessageSuccessful = false;
261 NSString *message = @"Sweet Potatoes";
262 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
263 pushMessageSuccessful = result;
264 pushMessageProcessed = true;
265 }];
266
267 EXPECT_TRUE(waitUntilEvaluatesToTrue([&] { return [[configuration websiteDataStore] _hasServiceWorkerBackgroundActivityForTesting]; }));
268
269 TestWebKitAPI::Util::run(&pushMessageProcessed);
270 EXPECT_TRUE(pushMessageSuccessful);
271
272 EXPECT_TRUE(waitUntilEvaluatesToTrue([&] { return ![[configuration websiteDataStore] _hasServiceWorkerBackgroundActivityForTesting]; }));
273
274 clearWebsiteDataStore([configuration websiteDataStore]);
275}
276
277TEST(PushAPI, firePushEventWithNoPagesFail)
278{
279 TestWebKitAPI::HTTPServer server({
280 { "/"_s, { mainBytes } },
281 { "/sw.js"_s, { {{ "Content-Type"_s, "application/javascript"_s }}, waitOneSecondScriptBytes } }
282 }, TestWebKitAPI::HTTPServer::Protocol::Http);
283
284 [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
285
286 auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
287 clearWebsiteDataStore([configuration websiteDataStore]);
288
289 auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
290 provider.setPermission(server.origin(), true);
291
292 auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
293 [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
294
295 expectedMessage = "Ready"_s;
296 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
297 [webView loadRequest:server.request()];
298
299 TestWebKitAPI::Util::run(&done);
300
301 [webView _close];
302 webView = nullptr;
303
304 terminateNetworkProcessWhileRegistrationIsStored(configuration.get());
305
306 // Push event for service worker without any related page, should fail.
307 pushMessageProcessed = false;
308 pushMessageSuccessful = false;
309 NSString *message = @"Rotten Potatoes";
310 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
311 pushMessageSuccessful = result;
312 pushMessageProcessed = true;
313 }];
314
315 EXPECT_TRUE(waitUntilEvaluatesToTrue([&] { return [[configuration websiteDataStore] _hasServiceWorkerBackgroundActivityForTesting]; }));
316
317 TestWebKitAPI::Util::run(&pushMessageProcessed);
318 EXPECT_FALSE(pushMessageSuccessful);
319 EXPECT_TRUE(waitUntilEvaluatesToTrue([&] { return ![[configuration websiteDataStore] _hasServiceWorkerBackgroundActivityForTesting]; }));
320
321 clearWebsiteDataStore([configuration websiteDataStore]);
322}
323
324TEST(PushAPI, firePushEventWithNoPagesTimeout)
325{
326 TestWebKitAPI::HTTPServer server({
327 { "/"_s, { mainBytes } },
328 { "/sw.js"_s, { {{ "Content-Type"_s, "application/javascript"_s }}, waitOneSecondScriptBytes } }
329 }, TestWebKitAPI::HTTPServer::Protocol::Http);
330
331 [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
332
333 // Disable service worker delay for the purpose of testing, push event should timeout after 1 second.
334 auto dataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
335 [dataStoreConfiguration setServiceWorkerProcessTerminationDelayEnabled:NO];
336 auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration.get()]);
337
338 auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
339 configuration.get().websiteDataStore = dataStore.get();
340 clearWebsiteDataStore([configuration websiteDataStore]);
341
342 auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
343 provider.setPermission(server.origin(), true);
344
345 auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
346 [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
347
348 expectedMessage = "Ready"_s;
349
350 @autoreleasepool {
351 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
352 [webView loadRequest:server.request()];
353 TestWebKitAPI::Util::run(&done);
354 }
355
356 terminateNetworkProcessWhileRegistrationIsStored(configuration.get());
357
358 // Push event for service worker without any related page, should timeout so fail.
359 pushMessageProcessed = false;
360 pushMessageSuccessful = false;
361 NSString *message = @"Timeless Potatoes";
362 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
363 pushMessageSuccessful = result;
364 pushMessageProcessed = true;
365 }];
366
367 EXPECT_TRUE(waitUntilEvaluatesToTrue([&] { return [[configuration websiteDataStore] _hasServiceWorkerBackgroundActivityForTesting]; }));
368
369 TestWebKitAPI::Util::run(&pushMessageProcessed);
370 EXPECT_FALSE(pushMessageSuccessful);
371 EXPECT_TRUE(waitUntilEvaluatesToTrue([&] { return ![[configuration websiteDataStore] _hasServiceWorkerBackgroundActivityForTesting]; }));
372
373 clearWebsiteDataStore([configuration websiteDataStore]);
374}
375
376#if WK_HAVE_C_SPI
377
378static constexpr auto pushEventsAndInspectedServiceWorkerScriptBytes = R"SWRESOURCE(
379let port;
380self.addEventListener("message", (event) => {
381 port = event.data.port;
382 port.postMessage(self.internals ? "Ready" : "No internals");
383});
384self.addEventListener("push", (event) => {
385 self.registration.showNotification("notification");
386 if (event.data.text() === 'first') {
387 self.internals.setAsInspected(true);
388 self.previousMessageData = 'first';
389 return;
390 }
391 if (event.data.text() === 'second') {
392 self.internals.setAsInspected(false);
393 if (self.previousMessageData !== 'first')
394 event.waitUntil(Promise.reject('expected first'));
395 return;
396 }
397 if (event.data.text() === 'third') {
398 if (self.previousMessageData !== 'second')
399 event.waitUntil(Promise.reject('expected second'));
400 return;
401 }
402 if (event.data.text() === 'fourth') {
403 if (self.previousMessageData !== undefined)
404 event.waitUntil(Promise.reject('expected undefined'));
405 return;
406 }
407 self.previousMessageData = event.data.text();
408 event.waitUntil(Promise.reject('expected a known message'));
409});
410)SWRESOURCE"_s;
411
412TEST(PushAPI, pushEventsAndInspectedServiceWorker)
413{
414 TestWebKitAPI::HTTPServer server({
415 { "/"_s, { mainBytes } },
416 { "/sw.js"_s, { {{ "Content-Type"_s, "application/javascript"_s }}, pushEventsAndInspectedServiceWorkerScriptBytes } }
417 }, TestWebKitAPI::HTTPServer::Protocol::Http);
418
419 [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
420
421 auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
422 clearWebsiteDataStore([configuration websiteDataStore]);
423
424 auto context = adoptWK(TestWebKitAPI::Util::createContextForInjectedBundleTest("InternalsInjectedBundleTest"));
425 [configuration setProcessPool:(WKProcessPool *)context.get()];
426
427 auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
428 provider.setPermission(server.origin(), true);
429
430 auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
431 [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
432
433 expectedMessage = "Ready"_s;
434 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
435 [webView loadRequest:server.request()];
436
437 TestWebKitAPI::Util::run(&done);
438
439 [webView _close];
440 webView = nullptr;
441
442 terminateNetworkProcessWhileRegistrationIsStored(configuration.get());
443
444 // Push event for service worker without any related page.
445 pushMessageProcessed = false;
446 pushMessageSuccessful = false;
447 NSString *message = @"first";
448 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
449 pushMessageSuccessful = result;
450 pushMessageProcessed = true;
451 }];
452 TestWebKitAPI::Util::run(&pushMessageProcessed);
453 EXPECT_TRUE(pushMessageSuccessful);
454
455 pushMessageProcessed = false;
456 pushMessageSuccessful = false;
457 message = @"second";
458 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
459 pushMessageSuccessful = result;
460 pushMessageProcessed = true;
461 }];
462 TestWebKitAPI::Util::run(&pushMessageProcessed);
463 EXPECT_TRUE(pushMessageSuccessful);
464
465 pushMessageProcessed = false;
466 pushMessageSuccessful = false;
467 message = @"third";
468 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
469 pushMessageSuccessful = result;
470 pushMessageProcessed = true;
471 }];
472 TestWebKitAPI::Util::run(&pushMessageProcessed);
473 EXPECT_FALSE(pushMessageSuccessful);
474
475 // We delay so that the timer to terminate service worker kicks in.
476 sleep(3);
477
478 pushMessageProcessed = false;
479 pushMessageSuccessful = false;
480 message = @"fourth";
481 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
482 pushMessageSuccessful = result;
483 pushMessageProcessed = true;
484 }];
485 TestWebKitAPI::Util::run(&pushMessageProcessed);
486 EXPECT_TRUE(pushMessageSuccessful);
487}
488
489static constexpr auto inspectedServiceWorkerWithoutPageScriptBytes = R"SWRESOURCE(
490let port;
491self.addEventListener("message", (event) => {
492 port = event.data.port;
493 port.postMessage(self.internals ? "Ready" : "No internals");
494});
495self.addEventListener("push", async (event) => {
496 self.registration.showNotification("notification");
497 if (event.data.text() === 'firstmessage-inspected' || event.data.text() === 'firstmessage-not-inspected') {
498 if (event.data.text() === 'firstmessage-inspected')
499 self.internals.setAsInspected(true);
500 if (self.previousMessageData !== undefined)
501 event.waitUntil(Promise.reject('unexpected state with inspected message'));
502 self.previousMessageData = 'inspected';
503 // Wait for client to go away before resolving the event promise;
504 let resolve;
505 event.waitUntil(new Promise(r => resolve = r));
506 while (true) {
507 const clients = await self.clients.matchAll({ includeUncontrolled : true });
508 if (!clients.length)
509 resolve();
510 }
511 return;
512 }
513 if (event.data.text() === 'not inspected') {
514 setTimeout(() => self.internals.setAsInspected(false), 10);
515 if (self.previousMessageData !== 'inspected')
516 event.waitUntil(Promise.reject('unexpected state with not inspected message'));
517 self.previousMessageData = 'not inspected';
518 return;
519 }
520 if (event.data.text() === 'last') {
521 if (self.previousMessageData !== undefined)
522 event.waitUntil(Promise.reject('unexpected state with last message'));
523 }
524});
525)SWRESOURCE"_s;
526
527static void testInspectedServiceWorkerWithoutPage(bool enableServiceWorkerInspection)
528{
529 TestWebKitAPI::HTTPServer server({
530 { "/"_s, { mainBytes } },
531 { "/sw.js"_s, { {{ "Content-Type"_s, "application/javascript"_s }}, inspectedServiceWorkerWithoutPageScriptBytes } }
532 }, TestWebKitAPI::HTTPServer::Protocol::Http);
533
534 [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
535
536 auto dataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] init]);
537 [dataStoreConfiguration setServiceWorkerProcessTerminationDelayEnabled:NO];
538 auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration.get()]);
539
540 auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
541 configuration.get().websiteDataStore = dataStore.get();
542 clearWebsiteDataStore([configuration websiteDataStore]);
543
544 auto context = adoptWK(TestWebKitAPI::Util::createContextForInjectedBundleTest("InternalsInjectedBundleTest"));
545 [configuration setProcessPool:(WKProcessPool *)context.get()];
546
547 auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
548 provider.setPermission(server.origin(), true);
549
550 auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
551 [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
552
553 expectedMessage = "Ready"_s;
554 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
555 [webView loadRequest:server.request()];
556
557 TestWebKitAPI::Util::run(&done);
558
559 // Push event for service worker without any related page.
560 pushMessageProcessed = false;
561 pushMessageSuccessful = false;
562 NSString *message = @"firstmessage-inspected";
563 if (!enableServiceWorkerInspection)
564 message = @"firstmessage-not-inspected";
565 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
566 pushMessageSuccessful = result;
567 pushMessageProcessed = true;
568 }];
569
570 // We delay so that the push message will happen before the unregistration of the service worker client.
571 sleep(0.5);
572
573 // Closing the web view should not terminate the service worker as service worker is inspected.
574 [webView _close];
575 webView = nullptr;
576
577 TestWebKitAPI::Util::run(&pushMessageProcessed);
578 EXPECT_TRUE(pushMessageSuccessful);
579
580 // We delay so that the timer to terminate service worker kicks in, at most up to the max push message allowed time, aka 2 seconds.
581 sleep(3);
582
583 // Send message at which point the service worker will not be inspected anymore and will be closed.
584 pushMessageProcessed = false;
585 pushMessageSuccessful = false;
586 message = @"not inspected";
587 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
588 pushMessageSuccessful = result;
589 pushMessageProcessed = true;
590 }];
591 TestWebKitAPI::Util::run(&pushMessageProcessed);
592 EXPECT_EQ(pushMessageSuccessful, enableServiceWorkerInspection);
593
594 // We delay so that the timer to terminate service worker kicks in, at most up to the max push message allowed time, aka 2 seconds.
595 sleep(3);
596
597 pushMessageProcessed = false;
598 pushMessageSuccessful = false;
599 message = @"last";
600 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
601 pushMessageSuccessful = result;
602 pushMessageProcessed = true;
603 }];
604
605 TestWebKitAPI::Util::run(&pushMessageProcessed);
606 EXPECT_TRUE(pushMessageSuccessful);
607}
608
609TEST(PushAPI, inspectedServiceWorkerWithoutPage)
610{
611 bool enableServiceWorkerInspection = true;
612 testInspectedServiceWorkerWithoutPage(enableServiceWorkerInspection);
613}
614
615TEST(PushAPI, uninspectedServiceWorkerWithoutPage)
616{
617 bool enableServiceWorkerInspection = false;
618 testInspectedServiceWorkerWithoutPage(enableServiceWorkerInspection);
619}
620
621static constexpr auto fireNotificationClickEventMainBytes = R"SWRESOURCE(
622<script>
623function log(msg)
624{
625 window.webkit.messageHandlers.sw.postMessage(msg);
626}
627
628const channel = new MessageChannel();
629channel.port1.onmessage = (event) => log(event.data);
630navigator.serviceWorker.onmessage = (event) => log(event.data);
631
632navigator.serviceWorker.register('/sw.js').then((registration) => {
633 if (registration.active) {
634 registration.active.postMessage({port: channel.port2}, [channel.port2]);
635 return;
636 }
637 worker = registration.installing;
638 worker.addEventListener('statechange', function() {
639 if (worker.state == 'activated')
640 worker.postMessage({port: channel.port2}, [channel.port2]);
641 });
642}).catch(function(error) {
643 log("Registration failed with: " + error);
644});
645</script>
646)SWRESOURCE"_s;
647
648static constexpr auto fireNotificationClickEventScriptBytes = R"SWRESOURCE(
649let port;
650self.addEventListener("message", (event) => {
651 port = event.data.port;
652 port.postMessage("Ready");
653});
654self.addEventListener("push", (event) => {
655 self.registration.showNotification("notification");
656 try {
657 if (!event.data) {
658 port.postMessage("Received: null data");
659 return;
660 }
661 const value = event.data.text();
662 port.postMessage("Received: " + value);
663 if (value != 'Sweet Potatoes')
664 event.waitUntil(Promise.reject('I want sweet potatoes'));
665 } catch (e) {
666 port.postMessage("Got exception " + e);
667 }
668});
669self.addEventListener("notificationclick", async (event) => {
670 for (let client of await self.clients.matchAll({includeUncontrolled:true}))
671 client.postMessage("Received notificationclick");
672});
673)SWRESOURCE"_s;
674
675TEST(PushAPI, fireNotificationClickEvent)
676{
677 TestWebKitAPI::HTTPServer server({
678 { "/"_s, { fireNotificationClickEventMainBytes } },
679 { "/sw.js"_s, { {{ "Content-Type"_s, "application/javascript"_s }}, fireNotificationClickEventScriptBytes } }
680 }, TestWebKitAPI::HTTPServer::Protocol::Http);
681
682 [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins];
683
684 auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
685
686 auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
687 provider.setPermission(server.origin(), true);
688
689 auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
690 [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
691
692 clearWebsiteDataStore([configuration websiteDataStore]);
693
694 expectedMessage = "Ready"_s;
695 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
696 [webView loadRequest:server.request()];
697
698 TestWebKitAPI::Util::run(&done);
699
700 provider.resetHasReceivedNotification();
701 auto& providerRef = provider;
702
703 done = false;
704 pushMessageProcessed = false;
705 pushMessageSuccessful = false;
706 NSString *message = @"Sweet Potatoes";
707 expectedMessage = "Received: Sweet Potatoes"_s;
708
709 [[configuration websiteDataStore] _processPushMessage:messageDictionary([message dataUsingEncoding:NSUTF8StringEncoding], [server.request() URL]) completionHandler:^(bool result) {
710 EXPECT_TRUE(providerRef.hasReceivedNotification());
711 pushMessageSuccessful = result;
712 pushMessageProcessed = true;
713 }];
714 TestWebKitAPI::Util::run(&done);
715
716 TestWebKitAPI::Util::run(&pushMessageProcessed);
717 EXPECT_TRUE(pushMessageSuccessful);
718
719 terminateNetworkProcessWhileRegistrationIsStored(configuration.get());
720
721 provider.simulateNotificationClick();
722
723 done = false;
724 expectedMessage = "Received notificationclick"_s;
725 TestWebKitAPI::Util::run(&done);
726
727 clearWebsiteDataStore([configuration websiteDataStore]);
728}
729
730#endif // WK_HAVE_C_SPI
731
732#endif // ENABLE(NOTIFICATIONS) && ENABLE(NOTIFICATION_EVENT)
Note: See TracBrowser for help on using the repository browser.